Docker Compose lets you define an entire multi-container application in a single YAML file. Instead of running multiple docker run commands with long flags, you describe your stack declaratively and bring it all up with one command.
Why Docker Compose?
In the previous lesson, you ran a web app, database, and cache with three separate docker run commands. That approach has problems:
- Hard to remember all the flags
- Easy to make mistakes
- Difficult to share with teammates
- No easy way to restart the entire stack
Docker Compose solves all of this.
Your First Compose File
Create a file called docker-compose.yml:
services:
web:
image: nginx
ports:
- "8080:80"
volumes:
- ./html:/usr/share/nginx/html:roStart it:
docker compose up -dStop it:
docker compose downCompose File Structure
A complete docker-compose.yml for a full-stack app:
services:
app:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgres://app:secret@db:5432/myapp
- REDIS_URL=redis://cache:6379
- NODE_ENV=production
depends_on:
- db
- cache
restart: unless-stopped
db:
image: postgres:16
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: secret
POSTGRES_DB: myapp
volumes:
- pgdata:/var/lib/postgresql/data
ports:
- "5432:5432"
restart: unless-stopped
cache:
image: redis:7-alpine
ports:
- "6379:6379"
restart: unless-stopped
volumes:
pgdata:Service Configuration Options
Here are the most commonly used options:
services:
my-service:
# Build from a Dockerfile
build:
context: .
dockerfile: Dockerfile.prod
args:
NODE_ENV: production
# Or use a pre-built image
image: node:20-alpine
# Container name (optional)
container_name: my-app
# Port mapping
ports:
- "3000:3000"
- "9229:9229"
# Environment variables
environment:
NODE_ENV: production
API_KEY: ${API_KEY} # From host environment or .env file
# Load env from a file
env_file:
- .env
# Mount volumes
volumes:
- ./src:/app/src # Bind mount
- node_modules:/app/node_modules # Named volume
# Start after these services
depends_on:
- db
- cache
# Restart policy
restart: unless-stopped
# Override the default command
command: ["node", "server.js"]
# Connect to specific networks
networks:
- frontend
- backend
# Resource limits
deploy:
resources:
limits:
memory: 512M
cpus: "1.0"Essential Compose Commands
# Start all services in the background
docker compose up -d
# Start and rebuild images
docker compose up -d --build
# Stop all services
docker compose down
# Stop and remove volumes (deletes persistent data)
docker compose down -v
# View running services
docker compose ps
# View logs
docker compose logs
# Follow logs for a specific service
docker compose logs -f app
# Restart a specific service
docker compose restart app
# Scale a service
docker compose up -d --scale app=3
# Execute a command in a running service
docker compose exec app bash
# Run a one-off command
docker compose run --rm app npm testEnvironment Variables
Compose supports multiple ways to pass environment variables:
Inline in the compose file:
services:
app:
environment:
- NODE_ENV=production
- PORT=3000From a .env file (loaded automatically if present):
# .env
POSTGRES_PASSWORD=secret
API_KEY=abc123services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
app:
environment:
API_KEY: ${API_KEY}From a separate env file:
services:
app:
env_file:
- .env.productiondepends_on and Health Checks
depends_on controls startup order, but by default it only waits for the container to start, not for the service inside to be ready. Use health checks for true readiness:
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: secret
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
app:
build: .
depends_on:
db:
condition: service_healthyNetworks in Compose
Compose creates a default network for all services. You can define custom networks:
services:
nginx:
image: nginx
networks:
- frontend
api:
build: ./api
networks:
- frontend
- backend
db:
image: postgres:16
networks:
- backend
networks:
frontend:
backend:The database is only reachable from the API, not from Nginx.
Development vs Production Compose
Use multiple Compose files or profiles for different environments:
docker-compose.yml (base):
services:
app:
build: .
environment:
- NODE_ENV=production
ports:
- "3000:3000"docker-compose.override.yml (development overrides, loaded automatically):
services:
app:
build:
target: development
environment:
- NODE_ENV=development
volumes:
- ./src:/app/src
command: ["npm", "run", "dev"]# Development (uses both files automatically)
docker compose up
# Production (only base file)
docker compose -f docker-compose.yml up -dProfiles
Group services that should only run in certain contexts:
services:
app:
build: .
ports:
- "3000:3000"
db:
image: postgres:16
volumes:
- pgdata:/var/lib/postgresql/data
adminer:
image: adminer
ports:
- "8080:8080"
profiles:
- debug
mailhog:
image: mailhog/mailhog
ports:
- "1025:1025"
- "8025:8025"
profiles:
- debug
volumes:
pgdata:# Start without debug tools
docker compose up -d
# Start with debug tools
docker compose --profile debug up -dFull-Stack Example: Next.js + PostgreSQL + Redis
services:
app:
build:
context: .
dockerfile: Dockerfile
ports:
- "3000:3000"
environment:
DATABASE_URL: postgres://app:secret@db:5432/myapp
REDIS_URL: redis://cache:6379
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
restart: unless-stopped
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: secret
POSTGRES_DB: myapp
volumes:
- pgdata:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d myapp"]
interval: 5s
timeout: 5s
retries: 5
restart: unless-stopped
cache:
image: redis:7-alpine
command: redis-server --maxmemory 128mb --maxmemory-policy allkeys-lru
restart: unless-stopped
volumes:
pgdata:# Start the entire stack
docker compose up -d
# Check all services are running
docker compose ps
# View combined logs
docker compose logs -f
# Tear everything down
docker compose downSummary
Docker Compose turns multi-container applications into simple, declarative YAML files. You learned how to define services, configure networks and volumes, manage environments, and use health checks for proper startup ordering. With Compose, your entire development stack is version-controlled and reproducible.