14.2.2 Production Secrets
A focused guide to Production Secrets, connecting core concepts with practical Docker and container operations.
Production secrets management for Docker covers how credentials, API keys, certificates, and other sensitive values reach a running container without ever being baked into an image, stored in plaintext on disk, or exposed through ordinary inspection commands, since these values carry consequences well beyond a misconfiguration if they leak.
Why environment variables are not a secrets mechanism
Environment variables are a convenient way to pass ordinary configuration, but they are a poor fit for secrets specifically because of how visible they are: docker inspect reveals them in plaintext, any process running inside the container can read the full environment of its own process, and they are frequently captured incidentally in crash logs, error reports, or debugging output.
docker inspect my-api --format '{{json .Config.Env}}'
A secret passed this way is recoverable by anyone with inspect access to the container, which in most environments is a much larger set of people than those who should be able to see a database password or signing key.
Docker secrets in Swarm
Docker's native secrets mechanism, available in Swarm mode, stores secret values encrypted in the cluster's Raft log and mounts them as in-memory files inside the container at /run/secrets/, rather than exposing them as environment variables:
echo "supersecretpassword" | docker secret create db_password -
services:
api:
image: my-api:latest
secrets:
- db_password
secrets:
db_password:
external: true
const fs = require('fs');
const dbPassword = fs.readFileSync('/run/secrets/db_password', 'utf8').trim();
Because the secret is mounted as a file rather than injected into the environment, it does not appear in docker inspect output and is not inherited by child processes the way an environment variable would be.
External secrets managers
Outside of Swarm, or in addition to it, dedicated secrets managers such as Vault, AWS Secrets Manager, or a cloud provider's equivalent service are commonly used as the actual source of truth, with the application fetching secrets at startup rather than receiving them passively:
docker run -e VAULT_ADDR=https://vault.example.com -e VAULT_ROLE=my-api my-api
const vault = require('node-vault')({ endpoint: process.env.VAULT_ADDR });
const { data } = await vault.read('secret/data/my-api/db');
This pattern centralizes rotation, access control, and audit logging for secrets in a system designed for exactly that purpose, rather than spreading credential handling across many individually managed files and environment variables.
File-based secrets with bind mounts
When a full secrets manager is not in use, mounting a secret as a read-only file from a path outside the image is still significantly better than an environment variable, because it avoids docker inspect exposure even without Swarm's native mechanism:
docker run -v /etc/secrets/db_password:/run/secrets/db_password:ro my-api
The file's permissions on the host should be restricted to the account running the Docker daemon and to no one else, since the bind mount only moves the exposure surface, it does not eliminate it.
chmod 600 /etc/secrets/db_password
chown root:root /etc/secrets/db_password
Keeping secrets out of the image entirely
A secret value should never be present in any layer of an image, even temporarily during a multi-stage build, since image layers persist in the build cache and registry independently of whether the final stage still references the value:
# WRONG: this leaves the secret in an image layer permanently
ENV DB_PASSWORD=supersecretpassword
# Correct: use a build secret that is never persisted in a layer
RUN --mount=type=secret,id=db_password \
DB_PASSWORD=$(cat /run/secrets/db_password) ./build-step.sh
BuildKit's --mount=type=secret makes the secret available only during the specific RUN instruction that requests it, and never writes it into the resulting image layer, which is the correct way to use a secret during a build step such as fetching a private dependency.
Rotation
A secrets strategy is incomplete without a rotation plan, since a credential that is never rotated is a credential that, once leaked, remains exploitable indefinitely:
docker secret create db_password_v2 - <<< "newpassword"
docker service update --secret-rm db_password --secret-add db_password_v2 my-api
Versioning secret names (db_password_v2) rather than overwriting a secret in place allows a rolling update to transition replicas from the old credential to the new one without a moment where some replicas have a secret and others do not.
Auditing access
Knowing who can retrieve a given secret value, and when they did, is part of a complete secrets strategy. A secrets manager typically provides this audit trail natively; an ad hoc file-based approach generally does not, which is one of the stronger reasons to prefer a dedicated secrets system once an environment grows beyond a handful of credentials.
vault audit list
Common mistakes
- Passing credentials as plain environment variables when a file-based secrets mechanism is available, leaving them exposed through
docker inspectand process environment dumps. - Embedding a secret in an image layer during a build step, where it persists in the build cache and registry even after later stages stop referencing it.
- Committing a
.envfile containing real production credentials to version control, permanently leaking the secret's value at the time of the commit. - Never rotating credentials, so a single past leak remains exploitable indefinitely.
- Relying entirely on host filesystem permissions to protect a secret file without restricting which accounts can run
docker inspectordocker execagainst the container that mounts it, leaving an alternate path to the same value.
Production secrets management for Docker is built on the same core idea regardless of which specific mechanism is used: secrets should be mounted as files or fetched at runtime from a dedicated store, never embedded in an image or passed as a plain environment variable, and rotated and audited as a matter of routine rather than only after an incident forces the question.