Docker Secrets Management: Secure Credential Handling in Containers
Stop hardcoding passwords in Dockerfiles. Learn Docker secrets, environment variables, secret files, and integration with external vaults.
Hardcoding passwords in Docker images or compose files is a security disaster waiting to happen. Proper secrets management keeps credentials secure, rotatable, and auditable.
The Problem: Insecure Secrets
Bad Practice 1: Hardcoded in Dockerfile
# ❌ NEVER DO THIS
ENV DB_PASSWORD=supersecret123
ENV API_KEY=sk-1234567890abcdef
Why it's bad:
- Secrets in image layers (visible with
docker history) - Pushed to registries with secrets exposed
- Can't rotate without rebuilding image
Bad Practice 2: Hardcoded in Compose File
# ❌ NEVER DO THIS
services:
db:
image: postgres
environment:
POSTGRES_PASSWORD: supersecret123
Why it's bad:
- Compose file in version control
- Secrets visible to anyone with repo access
- Same password across all environments
Bad Practice 3: .env File in Repo
# ❌ .env in git repo
DB_PASSWORD=supersecret123
API_KEY=sk-1234567890abcdef
Why it's bad:
- Accidentally committed to git
- Visible in pull requests
- Shared with entire team
Docker Secrets (Swarm Mode)
Docker Swarm provides native secrets management.
Creating Secrets
# Create secret from string
echo "supersecret123" | docker secret create db_password -
# Create secret from file
docker secret create api_key ./api_key.txt
# List secrets
docker secret ls
# Inspect secret (metadata only, not value)
docker secret inspect db_password
Using Secrets in Services
# docker-compose.yml (Swarm mode)
version: '3.8'
services:
db:
image: postgres
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
api:
image: myapp
environment:
API_KEY_FILE: /run/secrets/api_key
secrets:
- api_key
secrets:
db_password:
external: true
api_key:
external: true
Deploy:
docker stack deploy -c docker-compose.yml myapp
How Secrets Work
- Secrets stored encrypted in Raft log
- Only sent to nodes running services that need them
- Mounted in memory at
/run/secrets/<secret_name> - Not written to disk
- Not visible in
docker inspect
Accessing Secrets in Code
# Python example
def get_secret(secret_name):
secret_path = f'/run/secrets/{secret_name}'
with open(secret_path, 'r') as f:
return f.read().strip()
db_password = get_secret('db_password')
api_key = get_secret('api_key')
// Node.js example
const fs = require('fs');
function getSecret(secretName) {
return fs.readFileSync(`/run/secrets/${secretName}`, 'utf8').trim();
}
const dbPassword = getSecret('db_password');
Docker Compose Secrets (Non-Swarm)
Docker Compose supports secrets without Swarm.
File-Based Secrets
# docker-compose.yml
version: '3.8'
services:
db:
image: postgres
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
secrets:
db_password:
file: ./secrets/db_password.txt
Create the secret file:
mkdir -p secrets
echo "supersecret123" > secrets/db_password.txt
chmod 600 secrets/db_password.txt
Add to .gitignore:
# .gitignore
secrets/
External Secrets
version: '3.8'
services:
db:
image: postgres
secrets:
- db_password
secrets:
db_password:
external: true
Create secret first:
docker secret create db_password ./secrets/db_password.txt
Environment Variables (Better Than Nothing)
If you can't use Docker secrets, use environment variables with .env files.
.env File
# .env (NOT in git)
DB_PASSWORD=supersecret123
API_KEY=sk-1234567890abcdef
Compose File
# docker-compose.yml
version: '3'
services:
db:
image: postgres
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
api:
image: myapp
environment:
API_KEY: ${API_KEY}
Security Measures
# Restrict .env file permissions
chmod 600 .env
# Add to .gitignore
echo ".env" >> .gitignore
# Verify not in git
git check-ignore .env
# Should output: .env
Podman Secrets
Podman provides similar secrets management.
Creating Secrets
# Create secret
echo "supersecret123" | podman secret create db_password -
# List secrets
podman secret ls
# Inspect
podman secret inspect db_password
Using Secrets
# containers.conf or compose file
version: '3.8'
services:
db:
image: postgres
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- source: db_password
target: db_password
External Secret Managers
For production, integrate with dedicated secret managers.
HashiCorp Vault
# Start Vault server
vault server -dev
# Store secret
vault kv put secret/myapp/db password=supersecret123
# Retrieve secret
vault kv get secret/myapp/db
Integration with Docker:
import hvac
client = hvac.Client(url='http://vault:8200', token='your-token')
secret = client.secrets.kv.v2.read_secret_version(path='myapp/db')
db_password = secret['data']['data']['password']
AWS Secrets Manager
import boto3
client = boto3.client('secretsmanager')
response = client.get_secret_value(SecretId='myapp/db-password')
db_password = response['SecretString']
Azure Key Vault
from azure.keyvault.secrets import SecretClient
from azure.identity import DefaultAzureCredential
credential = DefaultAzureCredential()
client = SecretClient(vault_url='https://myvault.vault.azure.net/', credential=credential)
db_password = client.get_secret('db-password').value
Google Secret Manager
from google.cloud import secretmanager
client = secretmanager.SecretManagerServiceClient()
name = f'projects/my-project/secrets/db-password/versions/latest'
response = client.access_secret_version(name=name)
db_password = response.payload.data.decode('UTF-8')
Secret Rotation
Rotate secrets regularly without downtime.
Manual Rotation
# 1. Create new secret
echo "newpassword123" | docker secret create db_password_v2 -
# 2. Update service to use new secret
docker service update \
--secret-rm db_password \
--secret-add db_password_v2 \
myapp_db
# 3. Remove old secret
docker secret rm db_password
# 4. Rename new secret (optional)
docker secret create db_password - <<EOF
newpassword123
EOF
Automated Rotation
#!/bin/bash
# rotate-secrets.sh
# Generate new password
new_password=$(openssl rand -base64 32)
# Create new secret
echo "$new_password" | docker secret create db_password_new -
# Update service
docker service update \
--secret-rm db_password \
--secret-add db_password_new \
myapp_db
# Wait for rollout
sleep 10
# Remove old secret
docker secret rm db_password
# Rename
docker secret create db_password - <<< "$new_password"
docker secret rm db_password_new
FoxTerm Secrets Management
FoxTerm simplifies secrets management for Docker/Podman.
Visual Secrets Editor
- View all secrets in Compose stacks
- Edit secret values securely
- Rotate secrets with one click
- Audit log of secret access
Secure Storage
- Secrets stored in macOS Keychain
- Encrypted at rest
- Biometric authentication required
- Never logged or displayed in plain text
Integration
- Auto-detect secret references in compose files
- Prompt to create missing secrets
- Validate secret permissions
- Sync secrets across environments
Best Practices
- Never hardcode secrets — Use Docker secrets or external managers
- Rotate regularly — Change passwords every 90 days
- Limit access — Only grant secrets to services that need them
- Audit access — Log who accessed what secrets when
- Use strong secrets — 32+ characters, random generation
- Separate environments — Different secrets for dev/staging/prod
- Encrypt at rest — Use encrypted storage for secret files
- Use secret managers — Vault, AWS Secrets Manager, etc. for production
Conclusion
Secure secrets management is non-negotiable for production containers. Docker secrets, Podman secrets, and external vaults provide the tools you need to keep credentials secure.
FoxTerm makes secrets management visual and secure, with Keychain integration and one-click rotation.
FoxTerm Pro includes secrets management for Docker/Podman. Download at foxterm.app.