Advanced Docker Compose: Expert Tips & Tricks
You know the basics. Now let's become an expert. These are the tips and tricks that separate beginners from experts. The features that make Compose powerful. The patterns that make you productive.
🎯 The Big Picture​
Think of advanced Compose features like professional tools. Basic tools work. But professional tools make you faster. More efficient. More powerful. That's what these tips do.
These are the features that make you an expert. The patterns that save time. The tricks that solve problems.
Tip 1: Using Profiles​
Profiles let you run different service combinations:
services:
# Always runs
db:
image: postgres:16-alpine
volumes:
- postgres-data:/var/lib/postgresql/data
# Only runs with 'dev' profile
dev-tools:
image: node:18-alpine
profiles: ["dev"]
command: npm run dev
# Only runs with 'test' profile
test-runner:
image: node:18-alpine
profiles: ["test"]
command: npm test
# Only runs with 'monitoring' profile
prometheus:
image: prom/prometheus
profiles: ["monitoring"]
ports:
- "9090:9090"
Run specific profiles:
# Run only default services
docker compose up
# Run with dev profile
docker compose --profile dev up
# Run with multiple profiles
docker compose --profile dev --profile monitoring up
# Run all profiles
docker compose --profile "*" up
Why use profiles:
- Different environments (dev, test, prod)
- Optional services (monitoring, dev tools)
- Conditional services
- Better organization
Real-world example:
# Development
docker compose --profile dev up
# Testing
docker compose --profile test up
# Production with monitoring
docker compose --profile monitoring up
Tip 2: Using Extends​
Extends let you reuse service definitions:
base.yml:
services:
app-base:
build: .
environment:
NODE_ENV: production
networks:
- backend
docker-compose.yml:
services:
app:
extends:
file: base.yml
service: app-base
environment:
DB_HOST: db
depends_on:
- db
app-worker:
extends:
file: base.yml
service: app-base
command: node worker.js
environment:
WORKER_MODE: true
Why use extends:
- DRY (Don't Repeat Yourself)
- Shared configuration
- Easier maintenance
- Template pattern
Tip 3: Advanced Health Checks​
Powerful health check patterns:
services:
app:
image: my-app:1.0
healthcheck:
# HTTP health check
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
# Or custom script
test: ["CMD-SHELL", "/healthcheck.sh"]
# Or multiple commands
test: >
/bin/sh -c "
curl -f http://localhost:3000/health || exit 1 &&
ps aux | grep -v grep | grep node || exit 1
"
interval: 30s
timeout: 10s
retries: 3
start_period: 40s # Grace period before counting failures
Advanced patterns:
healthcheck:
# Database health check
test: ["CMD-SHELL", "pg_isready -U appuser -d myapp"]
# Redis health check
test: ["CMD", "redis-cli", "ping"]
# Custom script with timeout
test: ["CMD-SHELL", "timeout 5 /app/healthcheck.sh"]
# Check multiple conditions
test: >
/bin/sh -c "
curl -f http://localhost/health &&
curl -f http://localhost/metrics &&
[ -f /app/.ready ] || exit 1
"
Tip 4: Secrets Management​
Use Docker secrets for sensitive data:
Create secrets:
# Create secret file
echo "my-secret-password" > db_password.txt
chmod 600 db_password.txt
Use in Compose:
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
secrets:
db_password:
file: ./secrets/db_password.txt
# Or external secret
# external: true
Multiple secrets:
services:
app:
secrets:
- db_password
- api_key
- ssl_cert
secrets:
db_password:
file: ./secrets/db_password.txt
api_key:
file: ./secrets/api_key.txt
ssl_cert:
file: ./secrets/ssl_cert.pem
Why use secrets:
- Not in environment variables
- Not in image layers
- Secure file-based access
- Industry best practice
Tip 5: Configs Management​
Use configs for non-sensitive configuration:
services:
nginx:
image: nginx:alpine
configs:
- nginx_config
- ssl_config
configs:
nginx_config:
file: ./nginx/nginx.conf
ssl_config:
file: ./nginx/ssl.conf
Why use configs:
- Separate from code
- Version controlled
- Easy to update
- Not in images
Tip 6: Advanced Dependency Management​
Smart dependency patterns:
services:
db:
image: postgres:16-alpine
healthcheck:
test: ["CMD-SHELL", "pg_isready"]
interval: 10s
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
app:
build: .
depends_on:
# Wait for healthy
db:
condition: service_healthy
# Wait for started (no health check)
redis:
condition: service_started
# Wait for completed (one-time jobs)
migrations:
condition: service_completed_successfully
migrations:
build: .
command: npm run migrate
depends_on:
db:
condition: service_healthy
restart: "no" # One-time job
Dependency conditions:
service_started: Service started (default)service_healthy: Service is healthyservice_completed_successfully: Service completed (for jobs)
Tip 7: Resource Management​
Advanced resource limits:
services:
app:
image: my-app:1.0
deploy:
resources:
limits:
cpus: '2.0' # Maximum 2 CPUs
memory: 1G # Maximum 1GB RAM
reservations:
cpus: '0.5' # Guaranteed 0.5 CPU
memory: 512M # Guaranteed 512MB RAM
replicas: 3 # Run 3 instances
update_config:
parallelism: 1 # Update one at a time
delay: 10s # Wait 10s between updates
failure_action: rollback
restart_policy:
condition: on-failure
max_attempts: 3
window: 120s
Why resource limits:
- Prevent resource exhaustion
- Fair resource sharing
- Predictable performance
- Cost control
Tip 8: Advanced Networking​
Custom network configurations:
services:
app:
networks:
frontend:
ipv4_address: 172.20.0.10
backend:
aliases:
- app
- api
- backend
networks:
frontend:
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/16
gateway: 172.20.0.1
backend:
driver: bridge
internal: true # No external access
ipam:
driver: default
config:
- subnet: 172.21.0.0/16
Network features:
- Static IP addresses
- Network aliases
- Internal networks
- Custom subnets
Tip 9: Volume Optimization​
Advanced volume patterns:
services:
db:
volumes:
# Named volume with options
- postgres-data:/var/lib/postgresql/data
# Bind mount with consistency
- ./config:/etc/postgresql:ro,cached
# tmpfs for temporary data
- type: tmpfs
target: /tmp
tmpfs:
size: 100M
volumes:
postgres-data:
driver: local
driver_opts:
type: none
o: bind
device: /mnt/ssd/postgres-data
# Or NFS
# type: nfs
# o: addr=nfs-server,nolock,soft
# device: ":/exports/postgres"
Volume options:
:ro- Read-only:rw- Read-write (default):cached- Better performance on macOS:delegated- Even better performance:consistent- Strong consistency
Tip 10: Environment Variable Tricks​
Advanced environment variable patterns:
services:
app:
environment:
# From .env file
- DB_HOST=${DB_HOST}
# With default
- DB_PORT=${DB_PORT:-5432}
# From shell
- NODE_ENV=${NODE_ENV}
# Conditional
- DEBUG=${DEBUG:-false}
# Multiple values
- ALLOWED_HOSTS=${ALLOWED_HOSTS:-localhost,127.0.0.1}
env_file:
- .env
- .env.local # Overrides .env
- .env.${ENVIRONMENT:-development}
Environment variable tips:
- Use defaults for development
- Override for production
- Use multiple env files
- Document required variables
Tip 11: Build Optimization​
Advanced build patterns:
services:
app:
build:
context: .
dockerfile: Dockerfile.prod
args:
NODE_ENV: production
BUILD_DATE: ${BUILD_DATE}
GIT_COMMIT: ${GIT_COMMIT}
cache_from:
- my-app:latest
- my-app:${GIT_COMMIT}
target: production # Multi-stage build target
tags:
- my-app:latest
- my-app:${GIT_COMMIT}
Build optimization:
- Use build cache
- Multi-stage builds
- Build arguments
- Tag management
Tip 12: Logging Configuration​
Advanced logging:
services:
app:
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
compress: "true"
labels: "production"
tag: "{{.Name}}/{{.ID}}"
# Or use syslog
# logging:
# driver: "syslog"
# options:
# syslog-address: "tcp://localhost:514"
# syslog-facility: "daemon"
# tag: "app"
Logging drivers:
json-file- Default, file-basedsyslog- System loggingjournald- Systemd journalgelf- Graylogfluentd- Fluentdawslogs- CloudWatch
Tip 13: Conditional Services​
Run services conditionally:
services:
app:
image: my-app:1.0
# Only if ENABLE_MONITORING is set
prometheus:
image: prom/prometheus
profiles: ["${ENABLE_MONITORING:+monitoring}"]
# Runs if ENABLE_MONITORING is set
Or use extends with conditions:
services:
app:
extends:
file: base.yml
service: app-base
profiles: ["${ENVIRONMENT:-development}"]
Tip 14: Multi-File Compose​
Split large Compose files:
docker-compose.yml:
services:
app:
extends:
file: services/app.yml
service: app
include:
- services/database.yml
- services/cache.yml
- services/monitoring.yml
Or use multiple files:
docker compose -f docker-compose.yml -f docker-compose.prod.yml up
Tip 15: Development vs Production​
Use different files:
docker-compose.yml (base):
services:
app:
build: .
environment:
NODE_ENV: ${NODE_ENV:-development}
docker-compose.prod.yml (override):
services:
app:
image: my-app:1.0 # Use image instead of build
environment:
NODE_ENV: production
deploy:
replicas: 3
resources:
limits:
memory: 1G
Run:
# Development
docker compose up
# Production
docker compose -f docker-compose.yml -f docker-compose.prod.yml up
Real-World Expert Example​
Complete expert-level Compose file:
services:
# Database with advanced config
postgres:
image: postgres:16-alpine
container_name: app-postgres
environment:
POSTGRES_USER: ${DB_USER:-appuser}
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
POSTGRES_DB: ${DB_NAME:-myapp}
POSTGRES_INITDB_ARGS: "-E UTF8 --locale=C"
volumes:
- postgres-data:/var/lib/postgresql/data
- ./init:/docker-entrypoint-initdb.d:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-appuser}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
restart: unless-stopped
deploy:
resources:
limits:
cpus: '2.0'
memory: 2G
reservations:
cpus: '1.0'
memory: 1G
networks:
backend:
aliases:
- db
- postgres
secrets:
- db_password
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
profiles:
- database
- default
# Application with scaling
app:
build:
context: .
dockerfile: Dockerfile.prod
args:
NODE_ENV: production
environment:
NODE_ENV: ${NODE_ENV:-production}
DB_HOST: postgres
DB_PASSWORD_FILE: /run/secrets/db_password
depends_on:
postgres:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
restart: unless-stopped
deploy:
replicas: 3
update_config:
parallelism: 1
delay: 10s
failure_action: rollback
restart_policy:
condition: on-failure
max_attempts: 3
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
cpus: '0.5'
memory: 256M
networks:
- frontend
- backend
secrets:
- db_password
configs:
- app_config
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
# Monitoring (optional profile)
prometheus:
image: prom/prometheus:latest
profiles: ["monitoring"]
volumes:
- ./prometheus:/etc/prometheus:ro
- prometheus-data:/prometheus
ports:
- "9090:9090"
networks:
- monitoring
restart: unless-stopped
volumes:
postgres-data:
driver: local
prometheus-data:
driver: local
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: false
monitoring:
driver: bridge
name: app-monitoring
secrets:
db_password:
file: ./secrets/db_password.txt
configs:
app_config:
file: ./config/app.conf
My Take: Expert Patterns​
Here are the patterns I use daily:
1. Always use health checks - Know when services are ready 2. Use secrets for passwords - Never in environment variables 3. Use profiles for environments - Dev, test, prod 4. Set resource limits - Prevent exhaustion 5. Use extends for DRY - Don't repeat yourself 6. Configure logging - Know what's happening 7. Use configs for files - Separate from code 8. Document everything - Comments are your friend
The key: These patterns make you productive. They solve real problems. They're what experts use.
Memory Tip: Professional Tools Analogy​
Advanced Compose = Professional tools
Basic tools: Work, but slow Professional tools: Fast, efficient, powerful
Once you master these, you're an expert.
Key Takeaways​
- Profiles for environments - Different services per environment
- Extends for DRY - Reuse service definitions
- Secrets for security - Never hard-code passwords
- Health checks are essential - Know when ready
- Resource limits prevent issues - Control resources
- Logging helps debugging - Know what's happening
- Configs separate concerns - Files not in images
- Multi-file for organization - Split large files
What's Next?​
Congratulations! You're now a Docker Compose expert. These tips and tricks will make you productive. Next: Docker Hub.
Remember: These are the patterns that make you an expert. Use them. Master them. They solve real problems. They make you productive.