Docker Secrets
Sensitive data (passwords, tokens, certificates) that must be available to containers at runtime without being stored in image layers, environment variables, or compose files in plaintext. Docker provides BuildKit secret mounts for build-time and file-based secrets for runtime.
Docker Secrets — Handling Credentials Without Leaking Them
What Are Docker Secrets in Simple Terms?
The most common Docker security mistake is putting a database password in a Dockerfile ENV instruction or in a docker-compose.yml file. Both store the secret in plaintext — one bakes it permanently into the image, the other commits it to version control. Docker Secrets provides the correct alternative.
WRONG — secret baked into image layer: ENV DB_PASSWORD=supersecret docker history my-image # ENV DB_PASSWORD=supersecret <- visible to anyone WRONG — secret in compose file: environment: DB_PASSWORD: supersecret <- committed to git CORRECT — build-time secret: RUN --mount=type=secret,id=db_pass ... # not in any layer CORRECT — runtime secret: Pass via environment at docker run time from secret manager # not in image, not in gitBuildKit Secret Mounts (Build-Time)
# syntax=docker/dockerfile:1 FROM node:20-alpineWORKDIR /appCOPY package.json ./ # Secret available only during this RUN, never in any layerRUN --mount=type=secret,id=npm_token \ NPM_TOKEN=$(cat /run/secrets/npm_token) \ npm install --registry https://registry.npmjs.org COPY . .CMD ["node", "server.js"]# Pass secret at build timedocker buildx build \ --secret id=npm_token,src=$HOME/.secrets/npm-token \ -t myapp:latest . # Verify secret not in imagedocker history myapp:latest# No NPM_TOKEN visible anywhereRuntime Secrets — Correct Patterns
# PATTERN 1: Environment variable from a file# Store secret in a file, not hardcodedecho "supersecret" > /run/secrets/db_password docker run -d \ --env-file /run/secrets/env-file \ payment-api:latest # PATTERN 2: Docker Compose secrets# docker-compose.ymlservices: api: secrets: * db_password # secret available at /run/secrets/db_password inside container secrets: db_password: file: ./secrets/db_password.txt # dev only — file on host # App reads: fs.readFileSync('/run/secrets/db_password').toString().trim() # PATTERN 3: AWS Secrets Manager at runtime# Application fetches secret on startup:# const sm = new SecretsManagerClient()# const secret = await sm.send(new GetSecretValueCommand({SecretId: 'prod/db'}))# process.env.DB_PASSWORD = JSON.parse(secret.SecretString).passwordWhat Never to Do
# NEVER: secrets in ENVENV DB_PASSWORD=supersecret123 # NEVER: secrets in ARGARG GITHUB_TOKEN=ghp_secretRUN git clone https://${GITHUB_TOKEN}@github.com/... # NEVER: copying .env file into imageCOPY .env . # <- ALL secrets now in image layer foreverSecurityIf you have accidentally baked a secret into a pushed image, rotate the secret immediately — before removing the image. Removing the image from the registry does not help users who already pulled it. The secret is still in their local Docker cache. Rotation first, cleanup second.
Frequently Asked Questions
Why is putting a secret in an ENV instruction in a Dockerfile considered unsafe even if the container itself is private?
Every instruction in a Dockerfile becomes a permanent, immutable layer in the image, and `ENV`-baked secrets remain visible in that layer forever — anyone who can pull the image (including from a registry) can extract it with `docker history` or by inspecting the layer filesystem directly, regardless of what the running container's environment looks like later. This is true even if you later `unset` the variable in a subsequent layer; the earlier layer with the secret still exists in the image.
What's the right way to handle secrets at build time versus runtime?
At build time, use BuildKit's `RUN --mount=type=secret` which mounts the secret into the build step's filesystem without ever writing it to a layer. At runtime, avoid plain environment variables where possible (they're visible via `docker inspect` and process listings) and prefer file-based secrets mounted from a secrets manager, Docker Swarm secrets, or Kubernetes Secrets — none of which are foolproof against a compromised host, but all of which avoid the much more common mistake of a secret ending up committed inside an image layer.