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 Secrets: What Works, What Doesn't, and Secure Alternatives

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:

  1. Real Docker secrets in Swarm and how to reference them from Compose, and
  2. 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:

  1. Strong security properties: encrypted at rest and in transit.
  2. Service-scoped access: only services that declare the secret can read it.
  3. Runtime injection: not baked into image layers.
  4. Rotation workflow: create a new secret, update service, remove old secret.

If you use Compose secret files (non-Swarm):

  1. Keeps secrets out of images (still injected at runtime as a file).
  2. Cleaner app config: apps read /run/secrets/... rather than hardcoded values.
  3. 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

CommandDescription
docker secret createCreates a new secret
docker secret lsLists all secrets
docker secret inspectShows 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:

  1. Created independently of the Docker Compose file
  2. Referenced in the Compose file using external: true
  3. Provide separation of concerns and better security
  4. 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:

  • .env for 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

  1. Management: External secrets are managed outside the Compose file, internal secrets are defined within it.
  2. Security: External secrets offer better security since they’re not visible in the Compose file.
  3. Reusability: External secrets can be shared across multiple services and stacks.
  4. Deployment: Internal secrets are easier to deploy in dev but may require extra steps in production.
  5. 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_password instead of secret1.
  • 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 example secrets/).
  • Consider keeping a secrets.example/ directory with placeholder files, or document required filenames in a README.
  • Treat .env files 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:
    1. Create a new secret (with a versioned name).
    2. Update the service to add the new secret and switch the app to it.
    3. 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 *_FILE patterns 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:

  1. 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
  2. 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
  3. 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
  4. 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"]
  5. 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
  6. 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.