Docker Compose
Docker Compose
Docker Compose allows you to define and run multi-container applications. Instead of managing containers individually, you define your entire application stack in a single YAML file.
Why Docker Compose?
Managing multiple containers manually becomes unwieldy:
# Without Compose: Multiple long commands
docker network create myapp-network
docker run -d \
--name postgres \
--network myapp-network \
-e POSTGRES_PASSWORD=secret \
-v postgres-data:/var/lib/postgresql/data \
postgres:16
docker run -d \
--name redis \
--network myapp-network \
redis:7
docker run -d \
--name app \
--network myapp-network \
-e DATABASE_URL=postgres://postgres:secret@postgres:5432/app \
-e REDIS_URL=redis://redis:6379 \
-p 3000:3000 \
myapp:latestNote
Docker Compose simplifies this to a single command: docker compose up. All
configuration lives in one file, making it easy to understand, version
control, and share.
Getting Started
Compose File Basics
Create a docker-compose.yml (or compose.yml) file:
services:
app:
image: myapp:latest
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgres://postgres:secret@postgres:5432/app
- REDIS_URL=redis://redis:6379
depends_on:
- postgres
- redis
postgres:
image: postgres:16
environment:
POSTGRES_PASSWORD: secret
volumes:
- postgres-data:/var/lib/postgresql/data
redis:
image: redis:7
volumes:
postgres-data:Basic Commands
# Start all services in the foreground
docker compose up
# Start in detached mode (background)
docker compose up -d
# Stop all services
docker compose down
# Stop and remove volumes
docker compose down -v
# View running services
docker compose ps
# View logs
docker compose logs
docker compose logs -f app
# Execute command in a service
docker compose exec app shCompose File Reference
Version and Services
# Modern compose files don't need 'version:'
services:
web:
image: nginx:1.25Note
The version key is deprecated in recent Docker Compose. Just start with
services:.
Image Configuration
services:
# Use a pre-built image
redis:
image: redis:7-alpine
# Build from Dockerfile
app:
build: .
# Build with options
app:
build:
context: .
dockerfile: Dockerfile.prod
args:
NODE_ENV: production
target: productionPorts
services:
web:
image: nginx
ports:
# Short syntax
- "8080:80"
- "443:443"
# Long syntax
- target: 80
published: 8080
protocol: tcp
mode: hostEnvironment Variables
services:
app:
image: myapp
environment:
# List syntax
- NODE_ENV=production
- DATABASE_URL=postgres://localhost/db
# Or map syntax
environment:
NODE_ENV: production
DATABASE_URL: postgres://localhost/db
api:
image: myapi
# Load from file
env_file:
- .env
- .env.localEnvironment File
# .env file
POSTGRES_USER=admin
POSTGRES_PASSWORD=secret
POSTGRES_DB=myapp
API_KEY=abc123Volumes
services:
app:
image: myapp
volumes:
# Named volume
- app-data:/app/data
# Bind mount
- ./src:/app/src
# Read-only bind mount
- ./config:/app/config:ro
postgres:
image: postgres:16
volumes:
- postgres-data:/var/lib/postgresql/data
# Initialize with SQL scripts
- ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
volumes:
app-data:
postgres-data:Networks
services:
frontend:
networks:
- frontend-network
api:
networks:
- frontend-network
- backend-network
database:
networks:
- backend-network
networks:
frontend-network:
backend-network:
driver: bridgeDependencies
services:
app:
depends_on:
- postgres
- redis
# With health check conditions
app:
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
postgres:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5Warning
depends_on only waits for the container to start, not for the service inside
to be ready. Use health checks for services that need initialization time.
Health Checks
services:
api:
image: myapi
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40sResource Limits
services:
app:
image: myapp
deploy:
resources:
limits:
cpus: "0.50"
memory: 512M
reservations:
cpus: "0.25"
memory: 256MRestart Policies
services:
app:
image: myapp
restart: always # or: no, on-failure, unless-stopped
# With failure count
worker:
image: myworker
deploy:
restart_policy:
condition: on-failure
max_attempts: 3
delay: 5sBuilding with Compose
Simple Build
services:
app:
build: .
ports:
- "3000:3000"Advanced Build Options
services:
app:
build:
context: .
dockerfile: Dockerfile.prod
args:
- NODE_ENV=production
- APP_VERSION=1.0.0
target: production
cache_from:
- myapp:latest
labels:
- "com.example.app=myapp"
image: myapp:1.0.0Build Commands
# Build all services
docker compose build
# Build specific service
docker compose build app
# Build without cache
docker compose build --no-cache
# Pull base images before building
docker compose build --pull
# Build and start
docker compose up --buildComplete Examples
Full-Stack Application
# docker-compose.yml
services:
frontend:
build: ./frontend
ports:
- "3000:3000"
environment:
- REACT_APP_API_URL=http://localhost:4000
depends_on:
- api
api:
build: ./backend
ports:
- "4000:4000"
environment:
- DATABASE_URL=postgres://postgres:secret@postgres:5432/app
- REDIS_URL=redis://redis:6379
- JWT_SECRET=your-secret-key
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
volumes:
- ./backend/src:/app/src
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: secret
POSTGRES_DB: app
volumes:
- postgres-data:/var/lib/postgresql/data
- ./database/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
volumes:
- redis-data:/data
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/certs:/etc/nginx/certs:ro
depends_on:
- frontend
- api
volumes:
postgres-data:
redis-data:Development Setup
# docker-compose.dev.yml
services:
app:
build:
context: .
target: development
volumes:
- .:/app
- /app/node_modules # Exclude node_modules
ports:
- "3000:3000"
- "9229:9229" # Debugger
environment:
- NODE_ENV=development
command: npm run dev
postgres:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: dev
ports:
- "5432:5432"
volumes:
- postgres-dev:/var/lib/postgresql/data
volumes:
postgres-dev:Running Development Environment
# Use development compose file
docker compose -f docker-compose.dev.yml up
# Or combine with base file
docker compose -f docker-compose.yml -f docker-compose.dev.yml upTesting Setup
# docker-compose.test.yml
services:
test:
build:
context: .
target: test
environment:
- NODE_ENV=test
- DATABASE_URL=postgres://postgres:test@postgres:5432/test
depends_on:
postgres:
condition: service_healthy
command: npm test
postgres:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: test
POSTGRES_DB: test
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 2s
timeout: 2s
retries: 10Compose Commands Reference
Lifecycle Commands
# Start services
docker compose up
docker compose up -d # Detached
docker compose up --build # Rebuild images
docker compose up --force-recreate # Recreate containers
# Stop services
docker compose stop # Stop containers
docker compose down # Stop and remove
docker compose down -v # Also remove volumes
docker compose down --rmi all # Also remove images
# Restart
docker compose restart
docker compose restart apiViewing Status
# List services
docker compose ps
docker compose ps -a # Include stopped
# View logs
docker compose logs
docker compose logs -f # Follow
docker compose logs -f api # Specific service
docker compose logs --tail 100 # Last 100 lines
# View resource usage
docker compose topExecuting Commands
# Run command in running container
docker compose exec api sh
docker compose exec api npm run migrate
# Run command in new container
docker compose run --rm api npm test
docker compose run --rm -e DEBUG=true api shScaling Services
# Scale service to multiple instances
docker compose up -d --scale worker=3
# View scaled services
docker compose psNote
When scaling, avoid publishing host ports or use dynamic port allocation. Multiple containers can't bind to the same host port.
Managing Multiple Environments
Override Files
Docker Compose automatically loads docker-compose.override.yml:
# docker-compose.yml (base)
services:
app:
image: myapp:latest
# docker-compose.override.yml (development defaults)
services:
app:
build: .
volumes:
- .:/app# Automatically uses both files
docker compose up
# Explicitly specify files
docker compose -f docker-compose.yml -f docker-compose.prod.yml upEnvironment-Specific Files
project/
├── docker-compose.yml # Base configuration
├── docker-compose.override.yml # Development (loaded by default)
├── docker-compose.prod.yml # Production overrides
└── docker-compose.test.yml # Testing overrides# Development (uses override automatically)
docker compose up
# Production
docker compose -f docker-compose.yml -f docker-compose.prod.yml up
# Testing
docker compose -f docker-compose.yml -f docker-compose.test.yml upQuick Reference
Common Commands
| Command | Purpose |
|---|---|
docker compose up | Create and start containers |
docker compose up -d | Start in background |
docker compose down | Stop and remove containers |
docker compose ps | List running services |
docker compose logs | View service logs |
docker compose exec | Execute command in service |
docker compose build | Build service images |
docker compose pull | Pull service images |
docker compose restart | Restart services |
Compose File Keys
| Key | Purpose |
|---|---|
services | Define application services |
volumes | Define named volumes |
networks | Define networks |
configs | Define configuration files |
secrets | Define secrets |
In the next chapter, we'll explore advanced Docker Compose features including profiles, environment management, and production configurations.