Managing Secrets
Keep passwords and API keys out of images; inject them at runtime through environment variables, mounted files or secret stores.
Credentials such as passwords, API keys and certificates are secrets. They must never be baked into an image, where anyone who pulls it can read them.
What not to do
- Do not put secrets in
ENVorARGin a Dockerfile — they persist in image layers. - Do not commit
.envfiles to source control.
Safer approaches
- Pass secrets at runtime with
--env-fileor the orchestrator's secret system. - Mount secrets as files with Docker/Compose secrets, which appear under
/run/secrets. - Use a dedicated secret manager (Vault, cloud secret stores).
Example
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
secrets:
db_password:
file: ./db_password.txtWhen to use it
- A developer uses a .env file for local development secrets but ensures it is listed in both .gitignore and .dockerignore.
- A production team uses Docker Swarm secrets to mount a database password as a file at /run/secrets/db_password inside the container.
- A CI pipeline injects API keys as environment variables from a secrets manager at deploy time, never baking them into the image.
More examples
Inject secret via environment variable
Fetches the secret from AWS Secrets Manager at deploy time and passes it as an env var, never storing it in the image.
# Read secret from a secrets manager at deploy time
export DB_PASSWORD=$(aws secretsmanager get-secret-value \
--secret-id prod/db-password --query SecretString --output text)
docker run -d \
-e DB_PASSWORD="$DB_PASSWORD" \
myapp:latestUse Docker Swarm secret
Stores the password encrypted in Swarm and mounts it as a file in the container's /run/secrets directory.
# Create the secret
echo 'supersecret' | docker secret create db_password -
# Reference it in a service
docker service create \
--name api \
--secret db_password \
myapp:latest
# Available inside container at /run/secrets/db_passwordNever bake secrets in image (anti-pattern)
Illustrates the anti-pattern of setting secrets in ARG/ENV during build, and the correct approach of injecting at runtime.
# WRONG: secret is burned into image layer history
# ARG DB_PASSWORD
# ENV DB_PASSWORD=$DB_PASSWORD
# RUN echo "password: $DB_PASSWORD" > /config.yaml
# CORRECT: leave placeholder, inject at runtime
ENV DB_PASSWORD=''
CMD ["node", "index.js"]
Discussion