Environment Variables in Compose
Environment Variables in Compose
Three Ways to Set Environment Variables
services:
api:
image: myapi
# 1. Inline key=value
environment:
DB_HOST: db
DB_PORT: 5432
NODE_ENV: production
# 2. List format (less readable)
environment:
- DB_HOST=db
- DB_PORT=5432
# 3. From .env file
env_file:
- .env
- ./api/.env
- path: ./secrets.env
required: false
.env File
Place a .env file in the same directory as docker-compose.yml:
# .env
DB_PASSWORD=supersecret
API_KEY=abc123
REDIS_URL=redis://redis:6379
POSTGRES_DB=myapp
POSTGRES_USER=admin
POSTGRES_PASSWORD=secret
Variable Substitution
Compose supports ${VARIABLE} syntax in YAML files:
services:
db:
image: postgres:${POSTGRES_VERSION:-16}
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB:-myapp}
api:
image: myapi:${API_VERSION:-latest}
environment:
DATABASE_URL: postgresql://${DB_USER:-postgres}:${DB_PASSWORD}@db:5432/${POSTGRES_DB}
Syntax:
${VARIABLE}— Use variable, error if missing${VARIABLE:-default}— Use variable, default if missing or empty${VARIABLE-default}— Use variable, default only if missing${VARIABLE:?error}— Use variable, error with message if missing${VARIABLE:?error message}— Custom error message
Environment Variable Precedence
From lowest to highest priority:
- Compose file
environmentvalues - Shell environment variables (host)
.envfile in Compose directoryenv_filein Compose fileenvironmentin Compose file (overrides all)
# Shell variable overrides .env
MY_VAR=shell_value docker compose up -d
Multiple Environments
# Development
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d
# Production
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
# docker-compose.dev.yml
services:
api:
build:
target: development
environment:
NODE_ENV: development
volumes:
- ./src:/app/src
# docker-compose.prod.yml
services:
api:
build:
target: production
environment:
NODE_ENV: production
restart: always
Secrets and Configs
Secrets and Configs
Docker Secrets (Swarm Mode)
Secrets are encrypted at rest and only mounted in memory:
services:
db:
image: postgres:16
secrets:
- db_password
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
db_password:
file: ./secrets/db_password.txt
Configs (Compose V2)
Configs mount non-sensitive configuration files:
services:
nginx:
image: nginx:1.25
configs:
- source: nginx_conf
target: /etc/nginx/nginx.conf
mode: 0444
configs:
nginx_conf:
file: ./config/nginx.conf
Best Practices for Sensitive Data
Never do:
# BAD: Hardcoded secrets in docker-compose.yml
services:
db:
environment:
POSTGRES_PASSWORD: supersecret123
Do:
# GOOD: Use .env file (not committed to git)
# docker-compose.yml
services:
db:
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
# .env (in .gitignore)
DB_PASSWORD=supersecret123
# BETTER: Use env_file with separate secrets file
services:
db:
env_file:
- .env
- path: .secrets.env
required: false
.gitignore for Secrets
# .gitignore
.env
.env.local
.env.production
*.env
secrets/
Runtime Secrets (Docker Swarm)
# Create secret
echo "my_secret_password" | docker secret create db_pass -
# Use in compose
services:
db:
image: postgres:16
secrets:
- db_pass
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_pass
secrets:
db_pass:
external: true
Compose Configuration Reference
Compose Configuration Reference
Complete docker-compose.yml Example
services:
web:
build:
context: ./frontend
dockerfile: Dockerfile
args:
NODE_ENV: production
image: myapp/web:latest
container_name: web
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- static_files:/usr/share/nginx/html
networks:
- frontend
depends_on:
api:
condition: service_healthy
environment:
- API_URL=http://api:4000
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:80"]
interval: 30s
timeout: 5s
retries: 3
restart: unless-stopped
deploy:
resources:
limits:
cpus: "0.5"
memory: 256M
labels:
- "traefik.enable=true"
- "traefik.http.routers.web.rule=Host(`example.com`)"
api:
build:
context: ./api
dockerfile: Dockerfile
image: myapp/api:latest
ports:
- "4000:4000"
networks:
- frontend
- backend
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
env_file:
- .env
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:4000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 15s
restart: unless-stopped
stop_grace_period: 30s
db:
image: postgres:16
volumes:
- pgdata:/var/lib/postgresql/data
networks:
- backend
environment:
POSTGRES_DB: ${POSTGRES_DB:-myapp}
POSTGRES_USER: ${DB_USER:-postgres}
POSTGRES_PASSWORD: ${DB_PASSWORD}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-postgres}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 20s
restart: unless-stopped
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
networks:
- backend
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
volumes:
pgdata:
redis_data:
static_files:
networks:
frontend:
backend:
internal: true
Useful Commands
# Validate compose file
docker compose config
# Show resolved config
docker compose config --format json
# List services
docker compose ps
# View images used
docker compose images
# Watch for changes (Docker Compose v2.22+)
docker compose watch