Docker Compose is a tool for defining and running multi-container Docker applications. It uses YAML files to configure services, lets you start the full stack with a single command, helps keep development and production environments consistent, and simplifies the management of complex container setups.
Docker Compose Overview
What is Docker Compose?
Docker Compose is a tool for defining, running, and managing applications composed of multiple containers. It allows you to declaratively define services, networks, and volumes in a docker-compose.yml file and manage the entire application stack with a single command.
History of Docker Compose
| Year | Event | Description |
|---|---|---|
| 2013 | Fig project started | Orchard team begins developing Fig, a Docker container orchestration tool |
| 2014 | Docker acquires Fig | Docker acquires Orchard and rebrands Fig as Docker Compose |
| 2015 | Compose v1 stabilized | Docker Compose is integrated as an official Docker tool |
| 2020 | Compose Specification | Docker Compose specification is released as an open standard |
| 2021 | Compose v2 released | Compose v2, rewritten in Go, is integrated as a Docker CLI plugin |
| 2023 | Compose v2 becomes default | docker-compose command is replaced by docker compose as the default tool |
Why Docker Compose is Necessary
Modern web applications are rarely composed of a single container. Typically, multiple services such as web servers, application servers, databases, caches, and message queues work together. Managing this kind of setup with individual docker run commands leads to several common problems.
| Problem | Description |
|---|---|
| Command complexity | Long docker run commands must be executed for each container |
| Startup order management | Service startup order based on dependencies must be managed manually |
| Network configuration | Networks for inter-container communication must be created and connected manually |
| Environment reproducibility | Difficult to reproduce the same environment on different machines |
| Lack of documentation | Infrastructure configuration is not explicitly documented |
Docker Compose solves these problems by allowing you to define and manage the entire application stack through declarative configuration files.
docker-compose.yml File Structure
YAML File Structure
A
docker-compose.ymlfile is organized around top-level keys such as version, services, networks, volumes, configs, and secrets. Each key contains the detailed settings for that resource type.
Basic File Structure
# Compose file version (optional, can be omitted in Compose v2)
version: "3.8"
# Service definitions (required)
services:
web:
image: nginx:latest
ports:
- "80:80"
api:
build: ./api
ports:
- "8080:8080"
depends_on:
- db
db:
image: postgres:15
environment:
POSTGRES_PASSWORD: secret
# Network definitions (optional)
networks:
frontend:
backend:
# Volume definitions (optional)
volumes:
db-data:
# Secret definitions (optional)
secrets:
db_password:
file: ./secrets/db_password.txt
Main Service Options
| Option | Description | Example |
|---|---|---|
| image | Specify the image to use | image: nginx:latest |
| build | Specify Dockerfile path | build: ./app |
| ports | Port mapping | ports: ["8080:80"] |
| volumes | Volume mounts | volumes: ["./data:/data"] |
| environment | Environment variable settings | environment: ["NODE_ENV=prod"] |
| env_file | Environment variable file | env_file: .env |
| depends_on | Service dependencies | depends_on: ["db", "redis"] |
| networks | Networks to connect | networks: ["backend"] |
| restart | Restart policy | restart: always |
| command | Override start command | command: npm start |
| healthcheck | Health check settings | See example below |
Practical Development Environment Setup
Node.js + Express + MySQL + Redis Stack
The following example sets up a full-stack JavaScript development environment.
version: "3.8"
services:
# Node.js application
app:
build:
context: .
dockerfile: Dockerfile.dev
container_name: node-app
volumes:
- .:/app # Source code mount
- /app/node_modules # Preserve node_modules
ports:
- "3000:3000"
environment:
- NODE_ENV=development
- DB_HOST=mysql
- DB_PORT=3306
- DB_USER=root
- DB_PASSWORD=secret
- DB_NAME=myapp
- REDIS_HOST=redis
- REDIS_PORT=6379
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_started
networks:
- app-network
command: npm run dev
# MySQL database
mysql:
image: mysql:8.0
container_name: mysql-db
volumes:
- mysql-data:/var/lib/mysql
- ./init.sql:/docker-entrypoint-initdb.d/init.sql # Initialization script
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: myapp
ports:
- "3306:3306"
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
networks:
- app-network
# Redis cache
redis:
image: redis:7-alpine
container_name: redis-cache
volumes:
- redis-data:/data
ports:
- "6379:6379"
command: redis-server --appendonly yes
networks:
- app-network
# Adminer (database management UI)
adminer:
image: adminer:latest
container_name: adminer
ports:
- "8080:8080"
depends_on:
- mysql
networks:
- app-network
networks:
app-network:
driver: bridge
volumes:
mysql-data:
redis-data:
React + Spring Boot + PostgreSQL Stack
The following configuration shows a full-stack application with separate frontend and backend services.
version: "3.8"
services:
# React frontend
frontend:
build:
context: ./frontend
dockerfile: Dockerfile.dev
container_name: react-app
volumes:
- ./frontend:/app
- /app/node_modules
ports:
- "3000:3000"
environment:
- REACT_APP_API_URL=http://localhost:8080/api
- CHOKIDAR_USEPOLLING=true # Enable file change detection
depends_on:
- backend
networks:
- frontend-network
# Spring Boot backend
backend:
build:
context: ./backend
dockerfile: Dockerfile.dev
container_name: spring-app
volumes:
- ./backend:/app
- maven-cache:/root/.m2
ports:
- "8080:8080"
- "5005:5005" # Debug port
environment:
- SPRING_PROFILES_ACTIVE=dev
- SPRING_DATASOURCE_URL=jdbc:postgresql://postgres:5432/myapp
- SPRING_DATASOURCE_USERNAME=postgres
- SPRING_DATASOURCE_PASSWORD=secret
- JAVA_TOOL_OPTIONS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
depends_on:
postgres:
condition: service_healthy
networks:
- frontend-network
- backend-network
# PostgreSQL database
postgres:
image: postgres:15-alpine
container_name: postgres-db
volumes:
- postgres-data:/var/lib/postgresql/data
- ./init-scripts:/docker-entrypoint-initdb.d
environment:
POSTGRES_DB: myapp
POSTGRES_USER: postgres
POSTGRES_PASSWORD: secret
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
networks:
- backend-network
# pgAdmin (PostgreSQL management UI)
pgadmin:
image: dpage/pgadmin4:latest
container_name: pgadmin
environment:
PGADMIN_DEFAULT_EMAIL: [email protected]
PGADMIN_DEFAULT_PASSWORD: admin
ports:
- "5050:80"
depends_on:
- postgres
networks:
- backend-network
networks:
frontend-network:
driver: bridge
backend-network:
driver: bridge
volumes:
postgres-data:
maven-cache:
Django + Celery + RabbitMQ + PostgreSQL Stack
The following example is for a Python web application that uses asynchronous task processing.
version: "3.8"
services:
# Django web application
web:
build:
context: .
dockerfile: Dockerfile
container_name: django-web
volumes:
- .:/app
- static-data:/app/staticfiles
ports:
- "8000:8000"
environment:
- DEBUG=1
- DATABASE_URL=postgres://postgres:secret@postgres:5432/myapp
- CELERY_BROKER_URL=amqp://guest:guest@rabbitmq:5672//
- REDIS_URL=redis://redis:6379/0
depends_on:
postgres:
condition: service_healthy
rabbitmq:
condition: service_healthy
redis:
condition: service_started
command: python manage.py runserver 0.0.0.0:8000
networks:
- app-network
# Celery Worker
celery-worker:
build: .
container_name: celery-worker
volumes:
- .:/app
environment:
- DATABASE_URL=postgres://postgres:secret@postgres:5432/myapp
- CELERY_BROKER_URL=amqp://guest:guest@rabbitmq:5672//
- REDIS_URL=redis://redis:6379/0
depends_on:
- web
- rabbitmq
command: celery -A myapp worker -l info
networks:
- app-network
# Celery Beat (scheduler)
celery-beat:
build: .
container_name: celery-beat
volumes:
- .:/app
environment:
- DATABASE_URL=postgres://postgres:secret@postgres:5432/myapp
- CELERY_BROKER_URL=amqp://guest:guest@rabbitmq:5672//
depends_on:
- web
- rabbitmq
command: celery -A myapp beat -l info
networks:
- app-network
# Flower (Celery monitoring)
flower:
build: .
container_name: flower
ports:
- "5555:5555"
environment:
- CELERY_BROKER_URL=amqp://guest:guest@rabbitmq:5672//
depends_on:
- celery-worker
command: celery -A myapp flower
networks:
- app-network
# PostgreSQL
postgres:
image: postgres:15-alpine
container_name: postgres
volumes:
- postgres-data:/var/lib/postgresql/data
environment:
POSTGRES_DB: myapp
POSTGRES_USER: postgres
POSTGRES_PASSWORD: secret
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
networks:
- app-network
# RabbitMQ (message broker)
rabbitmq:
image: rabbitmq:3-management-alpine
container_name: rabbitmq
ports:
- "5672:5672"
- "15672:15672" # Management UI
healthcheck:
test: ["CMD", "rabbitmq-diagnostics", "check_running"]
interval: 10s
timeout: 5s
retries: 5
networks:
- app-network
# Redis (result backend)
redis:
image: redis:7-alpine
container_name: redis
networks:
- app-network
networks:
app-network:
driver: bridge
volumes:
postgres-data:
static-data:
Advanced Configuration
Environment-Specific Configuration File Separation
Environment-Specific Compose Files
Docker Compose can combine multiple files. By layering environment-specific override files on top of a base configuration, you can manage differences between development, staging, and production environments.
Base configuration (docker-compose.yml):
version: "3.8"
services:
web:
build: .
environment:
- DATABASE_URL=postgres://db:5432/myapp
depends_on:
- db
db:
image: postgres:15
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:
Development environment override (docker-compose.dev.yml):
version: "3.8"
services:
web:
build:
context: .
dockerfile: Dockerfile.dev
volumes:
- .:/app
ports:
- "3000:3000"
- "9229:9229" # Debugging port
environment:
- NODE_ENV=development
- DEBUG=true
db:
ports:
- "5432:5432"
environment:
POSTGRES_PASSWORD: devpassword
Production environment override (docker-compose.prod.yml):
version: "3.8"
services:
web:
build:
context: .
dockerfile: Dockerfile.prod
restart: always
environment:
- NODE_ENV=production
deploy:
replicas: 3
resources:
limits:
cpus: "0.5"
memory: 512M
db:
restart: always
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
secrets:
db_password:
external: true
Running for each environment:
# Development environment
docker compose -f docker-compose.yml -f docker-compose.dev.yml up
# Production environment
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
Health Check Configuration
Health checks periodically verify service status so you can detect unhealthy containers. Used together with depends_on conditions, they help services wait for healthy dependencies before starting.
services:
web:
image: nginx
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/health"]
interval: 30s # Check interval
timeout: 10s # Timeout
retries: 3 # Number of retries on failure
start_period: 40s # Wait time after startup
api:
build: .
depends_on:
web:
condition: service_healthy
db:
condition: service_healthy
Resource Limits
In production environments, you should limit container resource usage to ensure system stability.
services:
web:
image: nginx
deploy:
resources:
limits:
cpus: "0.5" # CPU 50% limit
memory: 256M # Memory 256MB limit
reservations:
cpus: "0.25" # Minimum CPU 25% guaranteed
memory: 128M # Minimum memory 128MB guaranteed
Logging Configuration
The following configuration helps manage container logs more effectively.
services:
web:
image: nginx
logging:
driver: "json-file"
options:
max-size: "10m" # Maximum log file size
max-file: "3" # Number of log files to retain
labels: "production"
env: "os,customer"
Docker Compose Commands
Basic Commands
| Command | Description |
|---|---|
docker compose up | Create and start services |
docker compose up -d | Start services in background |
docker compose up --build | Rebuild images and start |
docker compose down | Stop services and remove containers |
docker compose down -v | Remove volumes as well |
docker compose start | Start stopped services |
docker compose stop | Stop services (keep containers) |
docker compose restart | Restart services |
Monitoring and Debugging Commands
| Command | Description |
|---|---|
docker compose ps | Check service status |
docker compose logs | View all service logs |
docker compose logs -f web | View specific service logs in real-time |
docker compose top | View running processes |
docker compose exec web bash | Access container shell |
docker compose run web npm test | Run one-time command |
Build and Image Management Commands
| Command | Description |
|---|---|
docker compose build | Build service images |
docker compose build --no-cache | Build without cache |
docker compose pull | Download service images |
docker compose push | Push service images |
docker compose images | List service images |
Practical Usage Examples
# Start development environment
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d
# Rebuild only specific service
docker compose build --no-cache web
# View logs (last 100 lines, real-time)
docker compose logs -f --tail=100
# Access database
docker compose exec db psql -U postgres -d myapp
# Scale adjustment
docker compose up -d --scale web=3
# Clean up environment (including volumes)
docker compose down -v --remove-orphans
Troubleshooting
Common Problems and Solutions
| Problem | Cause | Solution |
|---|---|---|
| Port conflict | Port already in use on host | Change port number or terminate conflicting process |
| Volume permission error | User ID mismatch between container and host | Add user: "1000:1000" setting |
| Service startup failure | Dependent service not ready yet | Use healthcheck with condition |
| Network connection failure | Incorrect network configuration | Verify network names and connections |
| Image build failure | Dockerfile error or context issue | Check build logs and fix Dockerfile |
Debugging Commands
# Check detailed container information
docker compose ps -a
# Validate service configuration
docker compose config
# Check event logs
docker compose events
# Check networks
docker network ls
docker network inspect <network_name>
# Check volumes
docker volume ls
docker volume inspect <volume_name>
Performance Optimization Tips
# Build cache optimization
services:
web:
build:
context: .
cache_from:
- myapp:latest
# Remove unnecessary layers
services:
web:
build:
target: production # Specify multi-stage build target
Conclusion
Docker Compose gives you a practical way to define and manage complex multi-container applications. Keeping the full development environment in a single YAML file also makes it easier to version, share, and reproduce across a team.
Features such as environment-specific overrides, health checks, networks, and volumes help you start services in the right order, connect them cleanly, and persist data with less manual work. In practice, that leads to a more reliable setup and faster day-to-day development.