01Introduction
Secrets—passwords, API keys, certificates—are the keys to your kingdom. Mishandling them leads to breaches. While Kubernetes has built-in secrets management, Docker Compose requires different approaches. This guide covers practical solutions.
02Common Mistakes to Avoid
Before looking at solutions, understand what NOT to do with secrets.
1# BAD: Secrets in docker-compose.yml2services: 3 db: 4 environment: 5 - POSTGRES_PASSWORD=super_secret_12367# BAD: Secrets in committed .env file8# (.env should be in .gitignore)910# BAD: Secrets in Docker image11# (Anyone with image access sees them)1213# BAD: Secrets in command history14docker run -e SECRET=value myapp15# Now visible in: history, ps, /proc1617# BAD: Logging secrets18echo $API_KEY # Appears in logsSecrets in environment variables are visible via docker inspect. Anyone with Docker access can see them.
03The .env File Approach
The simplest approach: keep secrets in .env files that are never committed to version control.
1# 1. Create .env with secrets2cat > .env << 'EOF'3DB_PASSWORD=super_secure_generated_password4API_KEY=sk-1234567890abcdef5JWT_SECRET=another_random_string6EOF78# 2. Restrict permissions9chmod 600 .env1011# 3. Add to .gitignore12echo ".env" >> .gitignore1314# 4. Reference in docker-compose.yml15# environment:16# - DB_PASSWORD=${DB_PASSWORD}1718# 5. Provide .env.example for documentation19cat > .env.example << 'EOF'20DB_PASSWORD=change_me21API_KEY=your_api_key_here22JWT_SECRET=generate_random_string23EOFUse different .env files for different environments: .env.production, .env.staging, .env.development
04Docker Secrets (Swarm Feature)
Docker has a built-in secrets feature, but it requires Swarm mode. You can still use it with Compose for local secrets management.
1# docker-compose.yml2services: 3 db: 4 image: postgres:155 environment: 6 - POSTGRES_PASSWORD_FILE=/run/secrets/db_password7 secrets: 8 - db_password910 app: 11 image: myapp:1.012 secrets: 13 - db_password14 - api_key15 # App reads from /run/secrets/db_password1617secrets: 18 db_password: 19 file: ./secrets/db_password.txt20 api_key: 21 file: ./secrets/api_key.txt2223# Create secrets directory24# mkdir secrets25# echo "my_password" > secrets/db_password.txt26# chmod 600 secrets/*05File-Based Secrets Pattern
Many images support reading secrets from files instead of environment variables. Look for *_FILE variants.
1services: 2 postgres: 3 image: postgres:154 environment: 5 # Instead of POSTGRES_PASSWORD, use POSTGRES_PASSWORD_FILE6 - POSTGRES_PASSWORD_FILE=/run/secrets/postgres_password7 volumes: 8 - ./secrets/postgres_password:/run/secrets/postgres_password:ro910 mysql: 11 image: mysql:812 environment: 13 - MYSQL_ROOT_PASSWORD_FILE=/run/secrets/mysql_password14 volumes: 15 - ./secrets/mysql_password:/run/secrets/mysql_password:ro1617# Images supporting *_FILE pattern:18# - PostgreSQL, MySQL, MariaDB19# - WordPress20# - Nextcloud21# - Many others (check documentation)The :ro (read-only) mount prevents the container from modifying the secret file.
06HashiCorp Vault Integration
For more sophisticated needs, Vault provides centralized secrets management with access control, audit logs, and automatic rotation.
1services: 2 vault: 3 image: hashicorp/vault:latest4 cap_add: 5 - IPC_LOCK6 environment: 7 - VAULT_ADDR=http://127.0.0.1:82008 volumes: 9 - vault_data:/vault/data10 command: server -dev # Use proper config in production1112 # App fetches secrets from Vault13 app: 14 image: myapp:1.015 environment: 16 - VAULT_ADDR=http://vault:820017 - VAULT_TOKEN=${VAULT_TOKEN}18 depends_on: 19 - vault2021volumes: 22 vault_data: 2324# Basic Vault usage:25# vault kv put secret/myapp db_password=super_secret26# vault kv get secret/myappVault adds significant complexity. Only use it if you need features like secret rotation, detailed audit logs, or dynamic credentials.
07Infisical for Teams
Infisical is an open-source secrets manager designed for development teams. It syncs secrets to .env files.
1# Install Infisical CLI2# (see docs for your platform)34# Login5infisical login67# Pull secrets to .env8infisical export --env=prod > .env910# Run command with injected secrets11infisical run -- docker compose up -d1213# Or run Infisical server self-hosted14services:15 infisical:16 image: infisical/infisical:latest17 ports:18 - "8080:8080"19 environment:20 - ENCRYPTION_KEY=${ENCRYPTION_KEY}21 - MONGO_URL=${MONGO_URL}08SOPS for Encrypted Files
SOPS encrypts secret files so they can be safely committed to git. Decryption happens at deploy time.
1# Install SOPS2# brew install sops (macOS)3# apt install sops (Debian/Ubuntu)45# Create age key for encryption6age-keygen -o ~/.config/sops/age/keys.txt78# Create SOPS config9cat > .sops.yaml << 'EOF'10creation_rules:11 - path_regex: secrets/.*\.yaml$12 age: >-13 age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx14EOF1516# Encrypt a secrets file17sops -e secrets.yaml > secrets/secrets.enc.yaml1819# Decrypt and use20sops -d secrets/secrets.enc.yaml > .env21docker compose up -d22rm .env # Clean up2324# Now secrets.enc.yaml can be committed to gitSOPS supports AWS KMS, GCP KMS, Azure Key Vault, and PGP for encryption. Age is simplest for personal use.
09Runtime Secret Injection
Inject secrets at container start time using entrypoint scripts or init containers.
1services: 2 app: 3 image: myapp:1.04 volumes: 5 - ./entrypoint.sh:/entrypoint.sh:ro6 entrypoint: ["/entrypoint.sh"]7 command: ["./start-app.sh"]89# entrypoint.sh10#!/bin/sh11# Fetch secrets from secure location12export DB_PASSWORD=$(cat /run/secrets/db_password)13export API_KEY=$(curl -s http: //vault:8200/v1/secret/data/api | jq -r '.data.data.key')1415# Execute the real command16exec "$@"10Best Practices Summary
1. Never commit secrets to git (use .gitignore)
2. Use *_FILE environment variables when available
3. Restrict file permissions (chmod 600)
4. Rotate secrets regularly
5. Use different secrets per environment
6. Audit who has access to secrets
7. Log secret access (not values)
8. Have a breach response plan
1# Generate secure random secrets2openssl rand -base64 3234# Rotate a secret:5# 1. Generate new secret6# 2. Update in secrets manager/.env7# 3. Restart affected services8# 4. Verify services work9# 5. Revoke old secret if applicable1011# Audit secret access12# Check who has read access to secrets files13ls -la ./secrets/14# Check Docker access (anyone with docker = root)15getent group docker