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.
openrouting — dockerfile explainer
$ explain Dockerfile! ENV hardcodes DATABASE_PASSWORD highline 7fix: 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.
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.