Introduction
Docker Compose is the standard tool for defining and running multi-container Docker applications. With a single YAML file, you can configure all your services, networks, and volumes, then spin up the entire stack with one command. This guide covers everything from basic syntax to production best practices, with real examples you can use immediately.
What is Docker Compose?
Docker Compose lets you define a multi-container application in a compose.yaml file. Instead of running multiple docker run commands with long flag lists, you describe your entire stack declaratively:
# compose.yaml
services:
web:
image: nginx:alpine
ports:
- "8080:80"
volumes:
- ./html:/usr/share/nginx/html
depends_on:
- api
api:
build: ./api
environment:
- DATABASE_URL=postgres://db:5432/myapp
depends_on:
- db
db:
image: postgres:16
environment:
- POSTGRES_DB=myapp
- POSTGRES_PASSWORD=secret
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:Run docker compose up and all three services start together, connected on a shared network, with proper dependency ordering.
Core Concepts
Services
A service is a container definition. Each service runs from an image or a build context. Services on the same Compose file share a default network and can reference each other by name:
services:
api:
image: node:20-alpine
# api can reach db at hostname "db"
environment:
- DB_HOST=db
db:
image: postgres:16Networks
By default, Compose creates a bridge network for your app. You can define custom networks to isolate services:
services:
web:
networks:
- frontend
api:
networks:
- frontend
- backend
db:
networks:
- backend
networks:
frontend:
backend:Volumes
Volumes persist data across container restarts. Use named volumes for databases and bind mounts for local development:
services:
api:
volumes:
- ./src:/app/src # bind mount (dev hot-reload)
- node_modules:/app/node_modules # named volume
db:
volumes:
- pgdata:/var/lib/postgresql/data # named volume (persist DB)
volumes:
pgdata:
node_modules:Common Commands
| Command | What It Does |
|---|---|
| docker compose up | Start all services (foreground) |
| docker compose up -d | Start in background (detached) |
| docker compose down | Stop and remove containers, networks |
| docker compose down -v | Also remove named volumes |
| docker compose logs -f | Follow logs from all services |
| docker compose ps | List running services |
| docker compose build | Rebuild images |
| docker compose exec api sh | Open shell in running container |
Real-World Example: Full-Stack App
# compose.yaml - Full-stack application
services:
# React frontend (dev server)
web:
build:
context: ./frontend
target: dev
ports:
- "3000:3000"
volumes:
- ./frontend/src:/app/src
environment:
- VITE_API_URL=http://localhost:8080
depends_on:
- api
# Node.js API server
api:
build: ./api
ports:
- "8080:8080"
environment:
- DATABASE_URL=postgres://postgres:secret@db:5432/myapp
- REDIS_URL=redis://cache:6379
- JWT_SECRET=${JWT_SECRET}
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
# PostgreSQL database
db:
image: postgres:16-alpine
environment:
- POSTGRES_DB=myapp
- POSTGRES_PASSWORD=secret
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
# Redis cache
cache:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
pgdata:Environment Variables
Never hardcode secrets in your Compose file. Use environment variables:
# compose.yaml
services:
api:
environment:
- DATABASE_URL=${DATABASE_URL}
- JWT_SECRET=${JWT_SECRET}
env_file:
- .env.local# .env.local (add to .gitignore!) DATABASE_URL=postgres://postgres:secret@db:5432/myapp JWT_SECRET=your-super-secret-key-here
Best Practices
- Use compose.yaml, not docker-compose.yml - The new standard filename (Compose V2).
- Pin image versions - Use
postgres:16-alpine, notpostgres:latest. - Use healthchecks - Let Compose know when a service is truly ready, not just started.
- Use .env files for secrets - Never commit passwords to version control.
- Separate dev and prod - Use
compose.override.yamlfor dev-specific settings. - Validate before running - Use our Docker Compose Validator to catch errors.