When working with sensitive data in Docker containers, many developers face the challenge of securely passing credentials without:
- Hardcoding in Dockerfiles
- Exposing in run commands
- Committing to version control
While Docker Secrets provide an elegant solution, they're traditionally associated with Swarm clusters. Here's how we can achieve similar security in standalone containers.
1. Using Docker Compose with Secrets
Even without Swarm, Docker Compose (version 3.1+) supports secrets:
version: '3.1' services: myapp: image: myapp:latest secrets: - db_password environment: DB_PASS_FILE: /run/secrets/db_password secrets: db_password: file: ./secrets/db_password.txt
Key points:
- Secrets are mounted at /run/secrets/
- Use _FILE suffix for environment variables
- Secret files should have strict permissions (chmod 600)
2. Volume Mounting Secrets
A simple approach using bind mounts:
docker run -v /path/to/secrets:/secrets:ro myapp
Inside your application:
# Python example with open('/secrets/db_password') as f: db_password = f.read().strip()
3. Using .env Files with Caution
While not ideal, you can use .env files with proper precautions:
# .env.production DB_PASSWORD=supersecret # docker-compose.yml env_file: - .env.production
Critical security measures:
- Add .env* to .gitignore
- Set strict file permissions (chmod 600)
- Never commit to repositories
Secret Rotation Strategy
Implement a process for regular secret rotation:
#!/bin/bash # Example rotation script new_password=$(openssl rand -hex 32) echo $new_password > /secrets/db_password docker service update --secret-rm old_password --secret-add source=new_password,target=db_password myapp
Audit Logging
Monitor secret access attempts:
# Sample audit rule for Linux echo '-w /run/secrets/ -p wa -k docker_secrets' >> /etc/audit/rules.d/docker-secrets.rules
Integration with Vault Solutions
For enterprise-grade security, consider HashiCorp Vault integration:
# Python example using hvac import hvac client = hvac.Client(url='https://vault.example.com') secret = client.read('secret/data/myapp') db_password = secret['data']['data']['db_password']
When running a single-container Docker application, passing sensitive data via environment variables in the docker run
command is common but problematic:
docker run -e DB_PASSWORD=supersecret -e API_KEY=123abc myapp
This approach exposes credentials in:
- Shell history
- Process listings (
ps aux
) - Docker inspect output
- CI/CD logs
1. Using Docker Compose with External Files
Create a secrets
directory outside your project:
mkdir -p ~/docker-secrets
echo "supersecret" > ~/docker-secrets/db_password
chmod 600 ~/docker-secrets/*
Then in your docker-compose.yml
:
version: '3.8'
services:
app:
image: myapp
environment:
DB_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
secrets:
db_password:
file: ~/docker-secrets/db_password
2. Bind Mounting Secret Files
For pure docker run
scenarios:
docker run \
-v ~/docker-secrets:/run/secrets \
-e DB_PASSWORD_FILE=/run/secrets/db_password \
myapp
Your application should then read from the file path specified in the *_FILE
environment variable.
3. Using Docker Configs (Even Without Swarm)
While designed for Swarm, configs can work in single-container scenarios:
echo "supersecret" | docker config create db_password -
docker run \
--config source=db_password,target=/run/secrets/db_password \
-e DB_PASSWORD_FILE=/run/secrets/db_password \
myapp
For a Python application, here's how to handle *_FILE
pattern:
import os
def get_secret(env_var):
file_var = f"{env_var}_FILE"
if file_var in os.environ:
with open(os.environ[file_var], 'r') as f:
return f.read().strip()
return os.getenv(env_var)
db_password = get_secret('DB_PASSWORD')
For Node.js applications:
const fs = require('fs');
function getSecret(envVar) {
const fileVar = ${envVar}_FILE;
if (process.env[fileVar]) {
return fs.readFileSync(process.env[fileVar], 'utf8').trim();
}
return process.env[envVar];
}
const apiKey = getSecret('API_KEY');
- Set strict permissions (600) on secret files
- Never commit secret files to version control
- Rotate secrets regularly
- Use
.dockerignore
to prevent accidental inclusion - Consider using a secrets manager (Hashicorp Vault, AWS Secrets Manager) for production