docker.recipes
Security10 min read

Secrets Management in Docker Compose (without Kubernetes)

Securely manage passwords, API keys, and sensitive data in Docker Compose deployments.

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.yml
2services:
3 db:
4 environment:
5 - POSTGRES_PASSWORD=super_secret_123
6
7# BAD: Secrets in committed .env file
8# (.env should be in .gitignore)
9
10# BAD: Secrets in Docker image
11# (Anyone with image access sees them)
12
13# BAD: Secrets in command history
14docker run -e SECRET=value myapp
15# Now visible in: history, ps, /proc
16
17# BAD: Logging secrets
18echo $API_KEY # Appears in logs

Secrets 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 secrets
2cat > .env << 'EOF'
3DB_PASSWORD=super_secure_generated_password
4API_KEY=sk-1234567890abcdef
5JWT_SECRET=another_random_string
6EOF
7
8# 2. Restrict permissions
9chmod 600 .env
10
11# 3. Add to .gitignore
12echo ".env" >> .gitignore
13
14# 4. Reference in docker-compose.yml
15# environment:
16# - DB_PASSWORD=${DB_PASSWORD}
17
18# 5. Provide .env.example for documentation
19cat > .env.example << 'EOF'
20DB_PASSWORD=change_me
21API_KEY=your_api_key_here
22JWT_SECRET=generate_random_string
23EOF

Use 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.yml
2services:
3 db:
4 image: postgres:15
5 environment:
6 - POSTGRES_PASSWORD_FILE=/run/secrets/db_password
7 secrets:
8 - db_password
9
10 app:
11 image: myapp:1.0
12 secrets:
13 - db_password
14 - api_key
15 # App reads from /run/secrets/db_password
16
17secrets:
18 db_password:
19 file: ./secrets/db_password.txt
20 api_key:
21 file: ./secrets/api_key.txt
22
23# Create secrets directory
24# mkdir secrets
25# echo "my_password" > secrets/db_password.txt
26# 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:15
4 environment:
5 # Instead of POSTGRES_PASSWORD, use POSTGRES_PASSWORD_FILE
6 - POSTGRES_PASSWORD_FILE=/run/secrets/postgres_password
7 volumes:
8 - ./secrets/postgres_password:/run/secrets/postgres_password:ro
9
10 mysql:
11 image: mysql:8
12 environment:
13 - MYSQL_ROOT_PASSWORD_FILE=/run/secrets/mysql_password
14 volumes:
15 - ./secrets/mysql_password:/run/secrets/mysql_password:ro
16
17# Images supporting *_FILE pattern:
18# - PostgreSQL, MySQL, MariaDB
19# - WordPress
20# - Nextcloud
21# - 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:latest
4 cap_add:
5 - IPC_LOCK
6 environment:
7 - VAULT_ADDR=http://127.0.0.1:8200
8 volumes:
9 - vault_data:/vault/data
10 command: server -dev # Use proper config in production
11
12 # App fetches secrets from Vault
13 app:
14 image: myapp:1.0
15 environment:
16 - VAULT_ADDR=http://vault:8200
17 - VAULT_TOKEN=${VAULT_TOKEN}
18 depends_on:
19 - vault
20
21volumes:
22 vault_data:
23
24# Basic Vault usage:
25# vault kv put secret/myapp db_password=super_secret
26# vault kv get secret/myapp

Vault 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 CLI
2# (see docs for your platform)
3
4# Login
5infisical login
6
7# Pull secrets to .env
8infisical export --env=prod > .env
9
10# Run command with injected secrets
11infisical run -- docker compose up -d
12
13# Or run Infisical server self-hosted
14services:
15 infisical:
16 image: infisical/infisical:latest
17 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 SOPS
2# brew install sops (macOS)
3# apt install sops (Debian/Ubuntu)
4
5# Create age key for encryption
6age-keygen -o ~/.config/sops/age/keys.txt
7
8# Create SOPS config
9cat > .sops.yaml << 'EOF'
10creation_rules:
11 - path_regex: secrets/.*\.yaml$
12 age: >-
13 age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
14EOF
15
16# Encrypt a secrets file
17sops -e secrets.yaml > secrets/secrets.enc.yaml
18
19# Decrypt and use
20sops -d secrets/secrets.enc.yaml > .env
21docker compose up -d
22rm .env # Clean up
23
24# Now secrets.enc.yaml can be committed to git

SOPS 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.0
4 volumes:
5 - ./entrypoint.sh:/entrypoint.sh:ro
6 entrypoint: ["/entrypoint.sh"]
7 command: ["./start-app.sh"]
8
9# entrypoint.sh
10#!/bin/sh
11# Fetch secrets from secure location
12export DB_PASSWORD=$(cat /run/secrets/db_password)
13export API_KEY=$(curl -s http: //vault:8200/v1/secret/data/api | jq -r '.data.data.key')
14
15# Execute the real command
16exec "$@"

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 secrets
2openssl rand -base64 32
3
4# Rotate a secret:
5# 1. Generate new secret
6# 2. Update in secrets manager/.env
7# 3. Restart affected services
8# 4. Verify services work
9# 5. Revoke old secret if applicable
10
11# Audit secret access
12# Check who has read access to secrets files
13ls -la ./secrets/
14# Check Docker access (anyone with docker = root)
15getent group docker