Dockerfile check · high

COPY or ADD of a secret file (.env, keys, credentials)

COPY .env ./ (or id_rsa, .npmrc, credentials files) puts the file into an image layer. Deleting it in a later step only hides it from the final filesystem: the earlier layer still contains it, and anyone who can pull the image can extract it. Keep secret files out of the build and provide secrets with a BuildKit secret mount at build time or from a secret store at run time.

$ explain Dockerfile
! COPY copies .env, which looks like a secret high
line 6
fix: see below
$ 

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

Why it matters

Image layers are additive. A file removed by a later RUN rm is still stored in the layer that added it, and pulling the image downloads every layer.

The explainer rates .env files, SSH private keys (id_rsa, id_ed25519…), credentials, .npmrc, .netrc, .pypirc, .git-credentials and .dockercfg high. Key and certificate files (.pem, .key, .crt, .p12, .pfx, .jks) are medium, because some of them, such as CA bundles and public certificates, are harmless.

Files ending in .example, .sample, .template or .dist are not flagged, and nor are COPY --from copies between build stages.

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
COPY .env ./
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
USER 10001
HEALTHCHECK CMD ["node", "src/healthcheck.js"]
CMD ["node", "src/server.js"]

How to fix it

  • Remove the COPY of the secret file and add it to .dockerignore so a broader COPY can't pick it up either.
  • Load configuration at run time: pass the variables from your orchestrator's secret store, or mount the file as a secret (for example a Kubernetes Secret volume or Compose secrets).
  • If a build step needs a credential (an .npmrc, an SSH key), use RUN --mount=type=secret or RUN --mount=type=ssh so it is never written to a layer.
  • Rotate anything that was in an image you pushed.

When it's acceptable

  • Public certificates and CA bundles (.crt, .pem without a private key), which the medium-severity check can't tell apart from private keys.
  • Images built only for local use with dummy values in the file.

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 stage9 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 COPY (opens in a new tab)

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

    Files like .env, private keys and credentials copied into an image stay in that layer even if a later step deletes them, and anyone who can pull the image can extract them.

    Safer: Keep the file out of the build (.dockerignore), and use RUN --mount=type=secret for build-time secrets or inject runtime secrets at start.

  • 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

Does deleting a file in a later Dockerfile step remove it from the image?

No. It disappears from the final filesystem, but the layer that added it still contains it and can be extracted by anyone who pulls the image.

Should .env be in .dockerignore?

Yes, in most projects. It stops COPY . . from sweeping the file into the image. The explainer can't see your .dockerignore, so it flags explicit copies of secret files either way.

How do I use an SSH key or .npmrc during a Docker build safely?

Use BuildKit mounts: RUN --mount=type=ssh forwards your SSH agent to one step, and RUN --mount=type=secret exposes a file such as .npmrc to one step, without writing either to a layer.