Docker Compose Secrets: What Works, What Doesn't, and Secure Alternatives
Learn what Docker Compose secrets actually do today, when Swarm secrets are required, how to wire secret files safely, and which alternatives to use in production.
Docker Compose makes it easy to define and run multi-container apps. Once you need database passwords, API keys, or private keys, though, things get tricky: how do you inject secrets without accidentally leaking them into Git, image layers, logs, or environment variables?
Here’s what matters up front:
- “Docker secrets” with encryption are a Swarm feature. Plain Docker Compose doesn’t encrypt secrets at rest—it just mounts files. Real encryption and service-scoped access only happen in Docker Swarm.
- Docker Compose can still mount secret data as files, but the security comes down to your host filesystem, backups, and how you manage them.
I’ll cover two approaches:
- Real Docker secrets in Swarm and how to reference them from Compose, and
- Compose secret files for local and self-hosted setups, plus production alternatives.
Understanding “Secrets” in Docker: Compose vs Swarm
What are Docker Secrets (Swarm secrets)?
Docker secrets are a Swarm feature for managing sensitive data securely: passwords, API keys, TLS keys, and so on.
When you run Swarm services, secrets are:
- Encrypted at rest in the Swarm Raft log
- Encrypted in transit between Swarm nodes
- Exposed to containers as files (typically under
/run/secrets/<name>) - Scoped to services that explicitly request them
This is what the “real” Docker secrets security model looks like.
What are “secrets” in Docker Compose (non-Swarm)?
In plain Docker Compose (without Swarm), a secrets: entry is a convenient way to mount a file into the container (similar to a bind mount, but with standardized path and permissions). It does not automatically give you Swarm’s encrypted secret store.
So:
- Use Swarm secrets when you need strong security guarantees inside Docker itself.
- Use Compose secret files for local dev and small self-hosted setups, and protect them like any sensitive file.
Benefits (and what they depend on)
If you use Swarm secrets:
- Strong security properties: encrypted at rest and in transit.
- Service-scoped access: only services that declare the secret can read it.
- Runtime injection: not baked into image layers.
- Rotation workflow: create a new secret, update service, remove old secret.
If you use Compose secret files (non-Swarm):
- Keeps secrets out of images (still injected at runtime as a file).
- Cleaner app config: apps read
/run/secrets/...rather than hardcoded values. - Version-control friendly: the Compose file references secret files, and you keep those secret files out of Git.
Just remember: Compose secret files are only as secure as your host and your operational practices.
Option A (Production-grade): Docker Swarm Secrets
Docker secrets require Swarm. If you’re not running Swarm, skip to Option B.
1) Initialize Swarm (one-time)
docker swarm init
2) Create a secret
Avoid echo "secret" > file when possible (shell history and tooling can leak). Prefer reading from stdin:
printf '%s' 'mysupersecretpassword' | docker secret create my_db_password -
Or from a file:
docker secret create my_db_password db_password.txt
chmod 400 db_password.txt
List secrets:
docker secret ls
Common commands
| Command | Description |
|---|---|
docker secret create | Creates a new secret |
docker secret ls | Lists all secrets |
docker secret inspect | Shows secret metadata (not value) |
docker secret rm <secret> | Removes a secret |
Docker won’t let you read the secret value back via CLI. That’s by design.
Using Swarm secrets from a Compose file
If you deploy your stack to Swarm (for example via docker stack deploy), you can reference an external Swarm secret:
version: "3.8"
services:
myapp:
image: myapp:latest
secrets:
- my_db_password
environment:
DB_PASSWORD_FILE: /run/secrets/my_db_password
secrets:
my_db_password:
external: true
Accessing secrets in containers
Secrets are mounted as files under /run/secrets/<secret_name>:
with open('/run/secrets/my_db_password', 'r', encoding='utf-8') as f:
db_password = f.read().strip()
This “read from file” pattern is recommended across languages because it avoids leaking secrets through process listings and environment dumps.
Option B (Compose-friendly): Secret files mounted into containers
If you’re running plain Docker Compose (no Swarm), you can still use the secrets: key to mount sensitive files into containers.
This works well for:
- local development
- single-host self-hosting
- hobby or prototype deployments
Step 1: Create a local secrets directory (and ignore it in Git)
Create files like:
./secrets/db_password.txt./secrets/api_key.txt
Make sure they’re not committed to version control (add secrets/ to .gitignore).
Step 2: Define secrets in docker-compose.yml
services:
database:
image: mysql:latest
environment:
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
secrets:
db_password:
file: ./secrets/db_password.txt
Step 3: Access secrets in containers
cat /run/secrets/db_password
Remember: this approach is backed by your host filesystem. Protect the ./secrets/ directory with strict permissions and secure backups.
For a full reverse-proxy example, see: https://www.bitdoze.com/traefik-wildcard-certificate/
External vs file-based secrets (what “external” really means)
You’ll see two patterns:
- External secrets (
external: true): this means “the secret already exists in the platform”.- This is most relevant for Swarm secrets.
- File-based secrets (
file: ...): this means “mount this local file into the container as/run/secrets/<name>”.- This is what most people use in plain Docker Compose.
External Secrets
External secrets are created and managed outside of the Docker Compose file, typically using the Docker CLI or a secret management system.
Key characteristics:
- Created independently of the Docker Compose file
- Referenced in the Compose file using
external: true - Provide separation of concerns and better security
- Can be shared across multiple services and stacks
Important note: Compose “secrets from environment variables”
In Docker Compose, secrets come from files. Some older guides suggest you can define secret values directly inside the Compose file or from environment variables—don’t do that.
If you need to use environment variables, prefer:
.envfor non-sensitive configuration- a proper secret manager for sensitive values (Vault or a cloud provider)
- or generate the secret file during deployment and mount it (never commit it)
For more on env usage, see: https://www.bitdoze.com/docker-env-vars/
Key Differences
- Management: External secrets are managed outside the Compose file, internal secrets are defined within it.
- Security: External secrets offer better security since they’re not visible in the Compose file.
- Reusability: External secrets can be shared across multiple services and stacks.
- Deployment: Internal secrets are easier to deploy in dev but may require extra steps in production.
- Versioning: External secrets can be versioned independently of your application.
When to Use Each Type
-
Use external secrets for:
- Production environments
- Sensitive data that needs to be shared across multiple services
- When you need to manage secrets independently of your application code
-
Use internal secrets for:
- Development and testing
- Quick prototyping
- When the secret is specific to a single service
Best Practices (modern and practical)
When working with secrets, the core goal stays the same: avoid shipping secrets in images, Git, or logs, and minimize the blast radius if something leaks.
Naming conventions
- Choose descriptive names:
prod_db_passwordinstead ofsecret1. - Use consistent prefixes:
prod_for production secrets,dev_for development. - Avoid sensitive information in names themselves.
Example:
secrets:
prod_api_key:
external: true
dev_db_password:
external: true
Version control considerations
- Never commit secret values to Git.
- Add your secrets directory to
.gitignore(for examplesecrets/). - Consider keeping a
secrets.example/directory with placeholder files, or document required filenames in a README. - Treat
.envfiles as secrets if they contain passwords or tokens, and don’t commit them.
Example .gitignore entry:
*.env
secrets/
Rotating secrets
- Rotate secrets regularly to limit exposure.
- In Swarm, rotation typically looks like:
- Create a new secret (with a versioned name).
- Update the service to add the new secret and switch the app to it.
- Remove the old secret after rollout.
Also rotate credentials at the source (database user password, cloud key, etc.), not only inside Docker.
Example:
docker secret create db_password_v2 new_password.txt
docker service update --secret-rm db_password_v1 --secret-add db_password_v2 myservice
docker secret rm db_password_v1
Additional best practices
- Least privilege: only mount the secrets a service needs.
- Prefer file-based consumption: use
*_FILEpatterns instead of putting secrets in environment variables. - Don’t print secrets: avoid logging config objects that include secret paths or values.
- Lock down your host: if using Compose secret files, secure filesystem permissions and backups.
- Use a secret manager: for production, consider Vault or a cloud secret manager.
Example Use Cases
Here are common scenarios where Docker secrets boost security:
Database credentials
Securely manage database passwords without hardcoding them in your application or Docker files.
version: '3.8'
services:
db:
image: postgres
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
app:
image: myapp
secrets:
- db_password
environment:
DB_PASSWORD_FILE: /run/secrets/db_password
secrets:
db_password:
external: true
API keys
Safely use API keys in your services without exposing them in your code or configuration files.
version: '3.8'
services:
api_service:
image: api_service
secrets:
- api_key
environment:
API_KEY_FILE: /run/secrets/api_key
secrets:
api_key:
external: true
SSL certificates
Manage SSL certificates securely for services that require HTTPS.
version: '3.8'
services:
web:
image: nginx
secrets:
- site_certificate
- site_key
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
secrets:
site_certificate:
file: ./certs/site.crt
site_key:
file: ./certs/site.key
JWT signing keys
Securely manage keys used for signing JSON Web Tokens (JWTs) in authentication services.
version: '3.8'
services:
auth_service:
image: auth_service
secrets:
- jwt_private_key
- jwt_public_key
environment:
JWT_PRIVATE_KEY_FILE: /run/secrets/jwt_private_key
JWT_PUBLIC_KEY_FILE: /run/secrets/jwt_public_key
secrets:
jwt_private_key:
external: true
jwt_public_key:
external: true
Limitations and Alternatives (updated)
Swarm mode requirement (for real Docker secrets)
True Docker secrets require Swarm. If you are using plain Docker Compose, you don’t get Swarm’s encrypted secret store—only file mounts.
This means:
- If you want Docker-managed secret encryption and scoping, you need Swarm (or another orchestrator).
- For Kubernetes, use Kubernetes Secrets (ideally with encryption-at-rest and an external secret manager integration).
Other options for managing sensitive data
Given these limitations, here are better alternatives depending on your environment:
-
Secret files mounted via Compose
- Pros: simple, keeps secrets out of image layers, works everywhere
- Cons: security depends on host filesystem, backups, and ops discipline
-
Environment variables (use with caution)
- Pros: simple and widely supported
- Cons: easy to leak via process inspection, crash dumps, logs, or “show config” tooling
Example (avoid for highly sensitive secrets if you can):
services: app: image: myapp environment: DB_PASSWORD: mysecretpassword -
Cloud secret managers
- AWS Secrets Manager, SSM Parameter Store, Google Secret Manager, Azure Key Vault
- Pros: strong security posture, IAM-based access, rotation
- Cons: platform coupling and potential cost
-
HashiCorp Vault
- Pros: strong, platform-agnostic, dynamic secrets, auditability
- Cons: more operational complexity
Example integration:
services: app: image: myapp environment: - VAULT_ADDR=http://vault:8200 entrypoint: ["vault-agent", "-config=/vault-agent-config.hcl"] -
Kubernetes Secrets (plus external secret operators)
- Pros: first-class in k8s ecosystems; integrates well with secret stores
- Cons: requires k8s; base k8s secrets are not encrypted unless configured
-
Docker Config
- Similar to Docker secrets but for non-sensitive configuration data
- Can be used alongside secrets for comprehensive configuration management
When choosing an alternative, consider your specific security requirements, existing infrastructure, team expertise, and scalability needs.
Conclusion
Docker Compose can help you wire secrets into containers as files, but the strong “Docker secrets” security properties come from Swarm.
Use this decision rule:
- If you want Docker-managed encryption and service scoping: use Swarm secrets.
- If you’re on plain Compose: use secret files, lock them down, and consider a real secret manager for production deployments.
Either way, prefer applications reading secrets from /run/secrets/... and keep secret values out of images, Git, and logs.