Understanding Docker Compose Secrets
Secrets in Docker Compose provide a secure mechanism for injecting sensitive data — passwords, API keys, certificates, database connection strings — into containers at runtime without baking them into images or exposing them in environment variables. The feature relies on Docker’s native secret management infrastructure, which encrypts secrets in transit, stores them safely on the swarm manager, and delivers them only to services that explicitly request them. This article focuses on production-grade usage: what secrets are, why they matter, how to define and consume them in a Compose file, and best practices to keep your deployments airtight.
What Exactly Are Docker Secrets?
A secret is a named encrypted blob that Docker mounts as a read-only file into the container’s /run/secrets/<secret_name> directory. Unlike environment variables, secrets are not visible in docker inspect output, process lists, or container logs. They are managed by the Docker daemon’s secrets subsystem, which is available in two contexts:
- Docker Swarm mode — secrets are created via
docker secret createand stored in the Raft log of the swarm, distributed only to nodes that run replicas of the requesting service. - Docker Compose (non‑swarm, file‑based) — Compose supports a
secretssection where you point to a local file on the host. The file content is bind‑mounted into/run/secrets/at container startup. This is useful for development, CI pipelines, or single‑host production setups that do not need full swarm orchestration.
In both cases, the container sees a plaintext file at the designated mount point, but the secret never appears in the image layers, environment variables, or container definition.
Why Secrets Matter in Production
Hardcoding credentials inside images is a severe security anti‑pattern. Images are often pushed to registries, scanned, and shared across teams — leaking a database password there compromises the entire stack. Environment variables are slightly better but still appear in docker inspect, docker compose config, and process‑table dumps. Secrets solve these issues:
- Encryption at rest and in transit — In swarm mode, secrets are encrypted when stored and when transmitted to worker nodes (mutual TLS).
- Access control — Only services that explicitly list a secret in their Compose definition receive it; other services on the same node cannot read the file.
- Ephemeral delivery — Secrets are mounted as tmpfs filesystems, never touching the container’s writable layer. When the container stops, the secret disappears from that node’s memory‑backed filesystem.
- Audit trail — Secret creation and rotation can be logged and integrated with secrets‑management platforms (HashiCorp Vault, AWS Secrets Manager).
How to Use Docker Compose Secrets
The following walk‑through covers both swarm‑mode secrets (recommended for true production clusters) and file‑based secrets for simpler Compose deployments. All examples assume Compose file version 3.8 or newer, which is the minimum required for secrets support.
1. Swarm Mode Secrets
First, ensure your Docker Engine is in swarm mode. On the manager node, run:
docker swarm init
Create a secret from a file or standard input. For example, a database password:
echo "SuperSecret!Pass123" | docker secret create db_password -
Alternatively, read from a file:
docker secret create db_password ./secrets/db_password.txt
Secrets are immutable; to update, you must remove and re‑create (or use external secret management with a driver). List existing secrets:
docker secret ls
Next, define a Compose file (e.g., docker-compose.yml) that references the secret. The top‑level secrets block declares which secrets are available, and each service lists the secrets it needs.
version: "3.8"
secrets:
db_password:
external: true # tells Compose the secret was already created via `docker secret create`
services:
app:
image: myapp:latest
secrets:
- db_password
environment:
- DB_PASSWORD_FILE=/run/secrets/db_password
deploy:
replicas: 2
restart_policy:
condition: any
db:
image: postgres:15
secrets:
- db_password
environment:
- POSTGRES_PASSWORD_FILE=/run/secrets/db_password
In this example, external: true tells Compose not to try to create the secret itself but to rely on the pre‑existing swarm secret named db_password. The app and database services both mount the secret at /run/secrets/db_password. Many official images (like Postgres) support _FILE suffixed environment variables that read the secret from that path instead of requiring plain text.
Deploy the stack:
docker stack deploy -c docker-compose.yml mystack
Now every replica of app and db has the secret available. If you scale the app service up, new containers automatically receive the secret.
2. File‑Based Secrets (Non‑Swarm Compose)
For single‑host production or CI environments where swarm mode is not desired, Compose can mount secrets from local files. The syntax is similar but uses the file key inside the top‑level secrets block. Docker Compose v2.19+ is required; check your version with docker compose version.
Create a secret file on the host (ensure it is not committed to version control):
mkdir -p ./secrets
echo "SuperSecret!Pass123" > ./secrets/db_password.txt
chmod 600 ./secrets/db_password.txt
Compose file:
version: "3.8"
secrets:
db_password:
file: ./secrets/db_password.txt
services:
app:
image: myapp:latest
secrets:
- db_password
environment:
- DB_PASSWORD_FILE=/run/secrets/db_password
Launch with docker compose up -d. The file’s content is bind‑mounted into /run/secrets/db_password. The mount is read‑only inside the container. Note: unlike swarm secrets, the content is not encrypted at rest on the host — you are responsible for securing the source file (e.g., restricting filesystem permissions, using encrypted volumes).
3. Consuming Secrets Inside the Container
The secret appears as a regular file. Your application can read it directly:
# Read secret in a shell script
DB_PASSWORD=$(cat /run/secrets/db_password)
Or, in a programming language:
# Python example
with open('/run/secrets/db_password', 'r') as f:
db_password = f.read().strip()
Many official Docker images support _FILE variables out of the box. For example, the Postgres image checks POSTGRES_PASSWORD_FILE and uses its content if defined, falling back to POSTGRES_PASSWORD. Always prefer the _FILE variant for secrets.
Best Practices for Production Secrets
-
Never hardcode secrets in Compose files or images. Always reference external secrets or file paths. Use
.gitignoreto exclude local secret files from version control. - Prefer swarm‑mode secrets for multi‑node production deployments. The encryption, automatic distribution, and tmpfs delivery are battle‑tested. For single‑node setups, file‑based secrets are acceptable but you must protect the host directory.
-
Use external secret management drivers (e.g., HashiCorp Vault, Azure Key Vault) to avoid storing secrets in the swarm log long‑term. Create secrets with
docker secret create --driver vault ...and reference them in Compose asexternal: true. - Limit secret visibility — only attach a secret to services that absolutely need it. Compose allows fine‑grained assignments; a logging sidecar should not receive the database master password.
-
Rotate secrets regularly. Swarm secrets are immutable, so rotation involves creating a new secret (e.g.,
db_password_v2), updating the Compose file, redeploying, and then removing the old secret. Automate this via CI/CD. -
Combine secrets with read‑only root filesystems (
read_only: truein the service definition) and non‑root users to minimize blast radius if a container is compromised. - Validate secret existence in health checks or entrypoint scripts to fail fast if a secret is missing, rather than silently crashing later with obscure errors.
-
Monitor secret usage via Docker logs and audit daemon events (
docker events) for secret creation, deletion, and access. Integrate with your SIEM.
Conclusion
Docker Compose secrets turn credential management from a risky afterthought into a first‑class, declarative part of your stack definition. Whether you operate a full Docker Swarm cluster or a single‑host Compose deployment, secrets keep sensitive material out of images, environment dumps, and configuration files. By adopting the practices above — external secret management, least‑privilege attachment, immutable rotation, and _FILE‑based consumption — you harden your production environment without sacrificing developer velocity. Start migrating your plain‑text environment variables today, and let Docker handle the secure delivery so your application only sees what it needs, when it needs it.