Docker Compose¶
Running containers individually with docker run is fine until you need three services talking to each other and you're typing 200-character CLI flags at 2AM Docker Compose defines multi-container applications in YAML — one file to rule them all , one command to start everything
docker-compose.yml — Your Application Blueprint¶
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DATABASE_URL=postgres://user:pass@db:5432/myapp
depends_on:
- db
- redis
db:
image: postgres:16-alpine
volumes:
- pgdata:/var/lib/postgresql/data
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: myapp
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
pgdata:
Services — The Containers You're Running¶
Each service maps to one container. Docker Compose handles networking so services can reach each other by service name
Common service patterns:
services:
# Node.js backend
api:
build: ./api
ports:
- "3000:3000"
env_file: .env
volumes:
- ./api:/app # Mount for live reload in dev
# PostgreSQL database
db:
image: postgres:16-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
# Redis for caching
cache:
image: redis:7-alpine
command: redis-server --requirepass ${REDIS_PASSWORD}
# Nginx reverse proxy
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
depends_on:
- api
Networks — How Containers Talk¶
By default , Docker Compose creates one network and all services join it You can define custom networks for isolation
services:
app:
networks:
- frontend
- backend
db:
networks:
- backend
nginx:
networks:
- frontend
networks:
frontend:
backend:
internal: true # No external access to backend network
Service app can reach db by the hostname db (service name) Service nginx cannot reach db — network isolation prevents it
Volumes — Persistence That Survives Rebuilds¶
services:
db:
volumes:
- pgdata:/var/lib/postgresql/data # Named volume
- ./init.sql:/docker-entrypoint-initdb.d/init.sql # Bind mount init script
app:
volumes:
- .:/app # Bind mount for dev (live reload)
- /app/node_modules # Anonymous volume (node_modules from image)
volumes:
pgdata: # Named volume persists even after docker compose down
Volume types: * Named volumes — Docker-managed , survives compose down , use for databases * Bind mounts — host path mapped to container , use for dev live reload * Anonymous volumes — created on the fly , mostly avoid
Environment Variables — Don't Hardcode Secrets¶
services:
app:
# Method 1: inline (bad for secrets)
environment:
- NODE_ENV=production
# Method 2: .env file (better)
env_file: .env
# Method 3: from host environment (for CI)
environment:
- DATABASE_URL
Compose automatically reads .env file from the same directory You reference variables in compose YAML with ${VAR_NAME} syntax
Profiles — Run Subsets of Services¶
services:
app:
image: myapp
db:
image: postgres:16-alpine
redis:
image: redis:7-alpine
adminer:
image: adminer
profiles:
- tools # Only starts with --profile tools
mailhog:
image: mailhog/mailhog
profiles:
- tools # Only starts with --profile tools
prometheus:
image: prom/prometheus
profiles:
- monitoring
# Start only app + db + redis
docker compose up -d
# Start with tools profile
docker compose --profile tools up -d
# Start with everything
docker compose --profile "*" up -d
Fullstack App Example — The Works¶
version: '3.8'
services:
# React frontend
frontend:
build: ./frontend
ports:
- "3000:3000"
environment:
- REACT_APP_API_URL=http://localhost:4000
depends_on:
- api
# Express API
api:
build: ./api
ports:
- "4000:4000"
env_file: ./api/.env
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
# PostgreSQL
db:
image: postgres:16-alpine
volumes:
- pgdata:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
# Redis cache
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
command: redis-server --appendonly yes
# Nginx reverse proxy
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
depends_on:
- frontend
- api
volumes:
pgdata:
redis_data:
Essential Compose Commands¶
# Start all services
docker compose up -d
# Rebuild and start
docker compose up -d --build
# View logs
docker compose logs -f app
# Execute command in service
docker compose exec app bash
# Stop services (keeps volumes)
docker compose down
# Stop services AND remove volumes (careful — data gone)
docker compose down -v
# List running services
docker compose ps
# View resource usage
docker compose top
Prerequisites¶
Dockerfile section , Node.js section