The Problem with Environment Variables
Putting secrets in environment variables is the most common Docker anti-pattern. Environment variables leak through docker inspect, child processes inherit them, and they often end up in logs or error messages. Docker secrets solve this by mounting secrets as in-memory files that only exist for the duration of the container.
Docker Swarm Secrets
Docker secrets require Swarm mode. Even for a single node:
docker swarm init
Create a secret:
# From a string
echo "my-db-password" | docker secret create db_password -
# From a file
docker secret create db_password ./password.txt
# From stdin (no shell history leak)
docker secret create db_password -
# Type password, Ctrl+D
List secrets (values are never shown):
docker secret ls
# ID NAME CREATED
# abc123def456 db_password 2 minutes ago
Using Secrets in Services
docker service create \
--name web \
--secret db_password \
--secret api_key \
nginx:latest
Inside the container, secrets appear as files in /run/secrets/:
docker exec $(docker ps -q -f name=web) cat /run/secrets/db_password
The path is /run/secrets/<secret_name>. The file is a tmpfs — it exists only in memory for the container’s lifetime.
Application Integration
Your application reads secrets from files instead of environment variables:
# Python
with open('/run/secrets/db_password') as f:
db_password = f.read().strip()
# Node.js
const fs = require('fs');
const dbPassword = fs.readFileSync('/run/secrets/db_password', 'utf8').trim();
# Shell script
DB_PASSWORD=$(cat /run/secrets/db_password)
Rotating Secrets
Docker secrets are immutable once created. To rotate:
# Create new secret
echo "new-password" | docker secret create db_password_v2 -
# Update service to use new secret and remove old
docker service update \
--secret-rm db_password \
--secret-add db_password_v2 \
web_service
# After confirming the service is healthy, remove old secret
docker secret rm db_password
The service update triggers a rolling restart. Each container gets the new secret as it’s replaced.
Docker Compose Secrets (Without Swarm)
For local development without Swarm, Docker Compose supports file-based secrets:
# docker-compose.yml
version: '3.8'
services:
web:
image: nginx
secrets:
- db_password
secrets:
db_password:
file: ./secrets/db_password.txt
Compose creates a bind mount at /run/secrets/db_password. The file on disk is your responsibility to protect.
Docker Compose + Swarm Mode
When deploying Compose to Swarm, use external secrets:
version: '3.8'
services:
web:
image: nginx
secrets:
- db_password
environment:
- DB_PASSWORD_FILE=/run/secrets/db_password
secrets:
db_password:
external: true
Create the secret in Swarm first, then deploy:
docker secret create db_password ./password.txt
docker stack deploy -c docker-compose.yml myapp
Best Practices
Never log secrets: Application code must not log the contents of /run/secrets/. Audit your logging code.
Strip whitespace: echo and cat add trailing newlines. Always .strip() or .trim() the file contents.
Limit secret scope: Only attach secrets to services that need them. A frontend proxy doesn’t need the database password.
Use secret names, not values: Reference secrets by name in Orchestration. Never hardcode values in Compose files.
Audit secret access: Docker logs secret rotation events but not read access. For audit trails, use a secrets manager (Vault, AWS Secrets Manager) that records every access.
Comparison: Swarm Secrets vs HashiCorp Vault
Docker secrets: Simple, built-in, works for Swarm deployments. No access audit, no dynamic secrets, no encryption at rest beyond TLS.
HashiCorp Vault: Dynamic secrets, access audit, encryption at rest, integration with cloud providers. Heavier to operate.
For small teams running Swarm: Docker secrets work well. For enterprises or multi-cloud: invest in Vault or the cloud provider’s secrets manager.
Summary
Docker secrets replace environment variables for sensitive data. Create secrets via stdin to avoid shell history leaks, mount them at /run/secrets/, and rotate by creating new secrets and updating services. For Compose-based development without Swarm, use file-based secrets with proper filesystem permissions.