Compose Basics
Compose Basics
Complete docker-compose.yml
version: '3.8'
services:
web:
build:
context: .
dockerfile: Dockerfile
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://postgres:secret@db:5432/myapp
- REDIS_URL=redis://redis:6379
env_file:
- .env
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
volumes:
- ./src:/app/src # Bind mount (dev only)
- node_modules:/app/node_modules
networks:
- app-network
restart: unless-stopped
deploy:
resources:
limits:
memory: 512M
cpus: '1.0'
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 15s
db:
image: postgres:15-alpine
environment:
POSTGRES_DB: myapp
POSTGRES_USER: postgres
POSTGRES_PASSWORD: secret
volumes:
- postgres-data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
ports:
- "5432:5432"
networks:
- app-network
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis-data:/data
networks:
- app-network
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./certs:/etc/nginx/certs:ro
depends_on:
- web
networks:
- app-network
restart: unless-stopped
volumes:
postgres-data:
redis-data:
node_modules:
networks:
app-network:
driver: bridge
Basic Commands
# Start all services
docker compose up
# Start in background
docker compose up -d
# Stop all services
docker compose down
# Stop and remove volumes
docker compose down -v
# View logs
docker compose logs
docker compose logs -f web # Follow specific service
# List services
docker compose ps
# Execute command in service
docker compose exec web bash
docker compose exec db psql -U postgres
# Build/rebuild images
docker compose build
docker compose build --no-cache web
# Scale a service
docker compose up -d --scale web=3
# View resource usage
docker compose top
# Restart a service
docker compose restart web
# View configuration
docker compose config
Environment Variables and Secrets
Environment Variables and Secrets
Environment Variable Sources
services:
web:
# Inline environment variables
environment:
- NODE_ENV=production
- LOG_LEVEL=info
# From .env file
env_file:
- .env
- .env.production
# With defaults and variable substitution
environment:
- NODE_ENV=${NODE_ENV:-development}
- DATABASE_URL=postgresql://${DB_USER}:${DB_PASSWORD}@db:5432/${DB_NAME}
- REDIS_URL=redis://redis:${REDIS_PORT:-6379}
# .env file
NODE_ENV=production
DB_USER=postgres
DB_PASSWORD=s3cr3t_p4ssw0rd
DB_NAME=myapp
REDIS_PORT=6379
# docker-compose.yml references these automatically
# No need to reference .env in env_file if it's in the same directory
Docker Secrets (Swarm mode)
# For Docker Swarm (not Compose standalone)
services:
web:
secrets:
- db_password
- api_key
secrets:
db_password:
file: ./secrets/db_password.txt
api_key:
external: true # Pre-created secret
Development vs Production
# docker-compose.override.yml (dev - loaded automatically)
services:
web:
build:
target: development
volumes:
- ./src:/app/src # Hot reload
environment:
- NODE_ENV=development
- DEBUG=true
command: npm run dev
# docker-compose.prod.yml (production)
services:
web:
build:
target: production
environment:
- NODE_ENV=production
command: node dist/server.js
deploy:
replicas: 3
# Usage:
# Development: docker compose up (uses override automatically)
# Production: docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
Service Dependencies and Profiles
Service Dependencies and Profiles
Health Check Dependencies
services:
web:
depends_on:
db:
condition: service_healthy # Wait for healthy
redis:
condition: service_started # Just wait for start
db:
image: postgres:15
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 10
redis:
image: redis:7
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 10
Profiles
services:
web:
# Always starts (no profile)
image: myapp
db:
# Always starts
image: postgres:15
# Dev tools (only with --profile dev)
mailhog:
image: mailhog/mailhog
profiles: [dev]
adminer:
image: adminer
profiles: [dev]
# Monitoring (only with --profile monitoring)
prometheus:
image: prom/prometheus
profiles: [monitoring]
grafana:
image: grafana/grafana
profiles: [monitoring]
# Usage:
# docker compose up -d # Only web + db
# docker compose --profile dev up -d # web + db + mailhog + adminer
# docker compose --profile dev --profile monitoring up -d # All services
Init Containers Pattern
services:
# Init job that runs before the main service
db-init:
image: postgres:15
command: bash -c "
until pg_isready; do sleep 1; done &&
psql -U postgres -c 'CREATE DATABASE IF NOT EXISTS myapp;'
"
environment:
POSTGRES_PASSWORD: secret
networks:
- app-network
web:
image: myapp
depends_on:
db-init:
condition: service_completed_successfully
Complete Multi-Service Stack
# Full-stack development environment
services:
frontend:
build: ./frontend
ports:
- "5173:5173"
volumes:
- ./frontend/src:/app/src
networks:
- app-network
backend:
build: ./backend
ports:
- "8080:8080"
environment:
- DATABASE_URL=postgresql://postgres:secret@db:5432/myapp
depends_on:
db:
condition: service_healthy
networks:
- app-network
db:
image: postgres:15-alpine
environment:
POSTGRES_DB: myapp
POSTGRES_PASSWORD: secret
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 5
networks:
- app-network
mail:
image: mailhog/mailhog
ports:
- "1025:1025" # SMTP
- "8025:8025" # Web UI
profiles: [dev]
networks:
- app-network
adminer:
image: adminer
ports:
- "8081:8080"
profiles: [dev]
networks:
- app-network
volumes:
postgres-data:
networks:
app-network:
driver: bridge