Docker Compose
What is Docker Compose?
Docker Compose is a tool for defining and running multi-container Docker applications. Instead of manually starting multiple containers with individual docker run commands, Compose uses a YAML file to orchestrate all services, networks, and volumes as a single application unit.
Why Use Docker Compose?
Managing multiple containers manually becomes complex quickly. Docker Compose solves this by:
graph LR
A["YAML File
docker-compose.yml"] -->B["Single Command
docker-compose up"]
B -->C["Multiple Containers
Networked Together"]
C -->D["Services Share
Network & Storage"]
style A fill:#e1f5ff
style B fill:#fff3e0
style C fill:#e8f5e9
style D fill:#f3e5f5
Basic Structure
Simple Web Application with Database
version: '3.8'
services:
web:
build: .
ports:
- "5000:5000"
environment:
- DATABASE_URL=postgres://db:5432/myapp
depends_on:
- db
db:
image: postgres:15-alpine
environment:
- POSTGRES_DB=myapp
- POSTGRES_PASSWORD=secret
volumes:
- db_data:/var/lib/postgresql/data
volumes:
db_data:
Start everything with one command:
docker-compose up
Key Components
Services
Individual containers that make up the application. Services can reference each other by name.
services:
api:
build: ./api
ports:
- "3000:3000"
frontend:
build: ./frontend
ports:
- "80:80"
depends_on:
- api
Containers communicate using service names:
// frontend connecting to api service
const response = await fetch('http://api:3000/data');
Networks
Services automatically join a shared network. This enables service-to-service communication.
services:
web:
networks:
- frontend
api:
networks:
- frontend
- backend
db:
networks:
- backend
networks:
frontend:
backend:
Volumes
Persist data across container restarts or share data between containers.
services:
database:
image: postgres:15-alpine
volumes:
# Named volume (managed by Docker)
- postgres_data:/var/lib/postgresql/data
# Bind mount (host directory)
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
volumes:
postgres_data:
Common Commands
| Command | Purpose |
|---|---|
docker-compose up |
Start all services |
docker-compose up -d |
Start in background (detached) |
docker-compose down |
Stop and remove containers |
docker-compose ps |
List running containers |
docker-compose logs |
View container logs |
docker-compose exec SERVICE CMD |
Run command in service |
docker-compose build |
Build/rebuild images |
Real-World Example: Full Stack Application
version: '3.8'
services:
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
ports:
- "3000:3000"
environment:
- REACT_APP_API_URL=http://localhost:5000
depends_on:
- api
api:
build:
context: ./api
dockerfile: Dockerfile
ports:
- "5000:5000"
environment:
- DATABASE_URL=postgres://db:5432/myapp
- REDIS_URL=redis://cache:6379
depends_on:
- db
- cache
volumes:
- ./api:/app
db:
image: postgres:15-alpine
environment:
- POSTGRES_DB=myapp
- POSTGRES_USER=appuser
- POSTGRES_PASSWORD=secure_password
volumes:
- postgres_data:/var/lib/postgresql/data
- ./scripts/init.sql:/docker-entrypoint-initdb.d/init.sql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser"]
interval: 10s
timeout: 5s
retries: 5
cache:
image: redis:7-alpine
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
volumes:
postgres_data:
redis_data:
networks:
default:
name: app_network
Start the full application:
docker-compose up -d
# Check status
docker-compose ps
# View logs
docker-compose logs -f api
# Execute command
docker-compose exec db psql -U appuser -d myapp
Environment Variables
Use .env file for configuration:
# .env
POSTGRES_PASSWORD=production_password
API_PORT=5000
DEBUG=false
Reference in compose file:
services:
db:
environment:
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
api:
ports:
- "${API_PORT}:5000"
Development vs Production
Development Setup (docker-compose.yml)
services:
api:
build: .
volumes:
- .:/app
environment:
- DEBUG=true
Production Setup (docker-compose.prod.yml)
services:
api:
image: myregistry.azurecr.io/api:v1.0
restart: always
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
Deploy production version:
docker-compose -f docker-compose.prod.yml up -d
Common Pitfalls
- Hardcoding credentials - Use environment variables and .env files
- Not handling startup order - Use
depends_onand health checks - Mounting entire directories in development - Consider specific files only
- Using latest image tags in production - Pin specific versions
Key Takeaways
- Docker Compose simplifies multi-container applications through declarative YAML configuration
- Services automatically network together using service names for easy communication
- Volumes persist data across container restarts and enable data sharing between containers
depends_onand health checks ensure correct startup order and readiness detection- Use separate Compose files for development and production environments
- Environment variables manage configuration without hardcoding values
- Single command (
docker-compose up) starts entire application stacks
Next Steps: Create docker-compose.yml for current projects, implement health checks for all services, and establish separate development and production configurations.