Dockerfile check · high

Secrets in a Dockerfile ENV instruction

ENV DATABASE_PASSWORD=… writes the value into the image's configuration. Anyone who can pull the image can read it with docker inspect, it shows up in docker history, and every container started from the image inherits it. Secrets belong in the environment at run time, supplied by your orchestrator or a secrets manager, not in the image.

$ explain Dockerfile
! ENV hardcodes DATABASE_PASSWORD high
line 7
fix: see below
$ 

What the free Dockerfile explainer reports on the risky example below.

Why it matters

ENV values are part of the image config, not a layer you can delete later. docker inspect prints them, docker history shows the ENV instruction, and they travel with the image to every registry and machine it is pushed to or pulled on.

Overriding the variable at run time with docker run -e or a Compose environment entry changes what the process sees, but the original value is still inside the image for anyone who pulls it.

The Dockerfile itself is usually in version control, so a literal value there is also readable by everyone with repository access, and stays in the repository's history after you remove it.

Risky and safer Dockerfile

RiskyDockerfile
FROM node:22.11-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY src ./src
ENV NODE_ENV=production
ENV DATABASE_PASSWORD=change-me-in-prod
USER 10001
HEALTHCHECK CMD ["node", "src/healthcheck.js"]
CMD ["node", "src/server.js"]
SaferDockerfile
FROM node:22.11-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY src ./src
ENV NODE_ENV=production
USER 10001
HEALTHCHECK CMD ["node", "src/healthcheck.js"]
CMD ["node", "src/server.js"]

How to fix it

  • Delete the ENV line for the secret. Keep non-secret settings such as NODE_ENV in ENV if you like.
  • Supply the secret when the container starts: a Kubernetes Secret, Docker or Compose secrets, your platform's secret settings, or a secrets manager the app reads at startup.
  • If a build step needs the secret, use a BuildKit secret mount (RUN --mount=type=secret) instead of ENV or ARG.
  • Treat the value as exposed: rotate it, because older images and the repository history still contain it.

When it's acceptable

  • Throwaway local images whose value is a dummy, such as a test database password that guards nothing.
  • Names that look secret but hold no secret (for example a public key or a key file path). The rule matches on the variable name, so review and ignore it in that case.

Check your own Dockerfile

The risky example is loaded below. Paste your own file to check it for this and every other Dockerfile rule. It runs in your browser; nothing is uploaded.

Dockerfile

Parsed in your browser. Nothing is uploaded.

Explanation

1 stage10 instructions1 high1 low

Stage 0node:22.11-slim

  • Docs for FROM (opens in a new tab)

    Starts from the base image node, tag 22.11-slim.

    A version tag can be re-pushed to different content; only a digest is immutable. This applies to every tagged FROM in the file.

    Safer: Pin the digest as well (image:tag@sha256:<digest>) and let a bot open pull requests when it changes.

  • Docs for WORKDIR (opens in a new tab)

    Sets the working directory to /app for later instructions and the container. It is created if it does not exist.

  • Docs for COPY (opens in a new tab)

    Copies package.json, package-lock.json from your build context to ./ in the image.

  • Docs for RUN (opens in a new tab)

    Runs npm ci --omit=dev while building the image and saves the result as a new layer.

  • Docs for COPY (opens in a new tab)

    Copies src from your build context to ./src in the image.

  • Docs for ENV (opens in a new tab)

    Sets 1 environment variable: NODE_ENV=production.

  • Docs for ENV (opens in a new tab)

    Sets 1 environment variable: DATABASE_PASSWORD=••••••.

    A value set with ENV is stored in the image config, so anyone who can pull the image (or run docker inspect) can read DATABASE_PASSWORD.

    Safer: Don't put DATABASE_PASSWORD in the image. Inject it when the container starts, from your orchestrator's secret store or a secrets manager.

  • Docs for USER (opens in a new tab)

    Runs later instructions and the container's process as user 10001.

  • Docs for HEALTHCHECK (opens in a new tab)

    Runs node src/healthcheck.js to check the container is healthy.

  • Docs for CMD (opens in a new tab)

    Sets the default command to node src/server.js. It runs when the container starts, and anything passed to docker run replaces it.

Questions

Can people see ENV variables in a Docker image?

Yes. ENV values are stored in the image configuration, so anyone who can pull the image can read them with docker inspect, and docker history shows the instruction that set them.

How do I pass a secret to a Docker container without ENV in the Dockerfile?

Provide it at run time from your orchestrator's secret store or a secrets manager, for example a Kubernetes Secret or Compose secrets. For build-time secrets, use RUN --mount=type=secret.

Does the explainer flag ENV API_KEY=$API_KEY?

No. This rule flags only literal values under secret-looking names (PASSWORD, SECRET, TOKEN, API_KEY, names ending in _KEY and similar). Copying a build argument into ENV still bakes its value into the image, and an ARG with a secret-looking name such as API_KEY is flagged by the build-argument rule.