Stop hardcoding passwords! Docker Compose secrets provide a secure way to handle sensitive data.

Basic secret setup

Define secrets and use them in services:

secrets:
  db_password:
    file: ./secrets/db_password.txt
  api_key:
    file: ./secrets/api_key.txt

services:
  app:
    image: myapp:latest
    secrets:
      - db_password
      - api_key
    environment:
      DB_PASSWORD_FILE: /run/secrets/db_password
      API_KEY_FILE: /run/secrets/api_key

Secrets appear as files in /run/secrets/ inside containers.

Reading secrets in your app

Node.js example:

const fs = require('fs');

function getSecret(name) {
  try {
    return fs.readFileSync(`/run/secrets/${name}`, 'utf8').trim();
  } catch (err) {
    return process.env[name]; // Fallback for dev
  }
}

const dbPassword = getSecret('db_password');

Python example:

def get_secret(name):
    try:
        with open(f'/run/secrets/{name}') as f:
            return f.read().strip()
    except FileNotFoundError:
        return os.environ.get(name)  # Fallback

Environment variables as secrets

For development, use environment variables:

secrets:
  db_password:
    environment: DB_PASSWORD
  api_key:
    environment: API_KEY

services:
  app:
    image: myapp
    secrets:
      - db_password
      - api_key

Run with:

DB_PASSWORD=secret API_KEY=key123 docker compose up

Multiple environments

Use different secret sources per environment:

# compose.yml (base)
services:
  app:
    image: myapp
    secrets:
      - db_password

# compose.dev.yml
secrets:
  db_password:
    environment: DB_PASSWORD

# compose.prod.yml
secrets:
  db_password:
    file: /secure/vault/db_password

Secret permissions

Control access within containers:

services:
  app:
    image: myapp
    secrets:
      - source: db_password
        target: database_password  # Rename in container
        uid: '1000'
        gid: '1000'
        mode: 0400  # Read-only for owner

External secrets

Use secrets from Docker Swarm or external sources:

secrets:
  db_password:
    external: true
    external_name: prod_db_password

Common patterns

Database connection:

services:
  postgres:
    image: postgres:15
    secrets:
      - postgres_password
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password

API keys:

services:
  api:
    image: api:latest
    secrets:
      - stripe_key
      - jwt_secret
    command: >
      sh -c "
      export STRIPE_KEY=$$(cat /run/secrets/stripe_key) &&
      export JWT_SECRET=$$(cat /run/secrets/jwt_secret) &&
      npm start"

Pro tip

Never commit secrets. Always use .gitignore 😅.

Further reading