Introduction to Docker Compose
Introduction to Docker Compose
Docker Compose is a tool for defining and running multi-container applications. Instead of running multiple docker run commands with complex flags, you define your entire application stack in a single docker-compose.yml file.
Why Compose?
Without Compose, running a full-stack app requires:
# Manual: error-prone, hard to repeat
docker network create appnet
docker run -d --name db --network appnet -e POSTGRES_PASSWORD=secret postgres:16
docker run -d --name api --network appnet -e DB_HOST=db myapi
# Wait for db to be ready...
docker run -d --name web -p 80:80 --network appnet mywebapp
With Compose:
# One command does everything
docker compose up -d
docker-compose.yml Structure
services:
web:
image: nginx:1.25
ports:
- "80:80"
depends_on:
- api
networks:
- frontend
api:
build: ./api
environment:
- DB_HOST=db
- DB_PASSWORD=secret
depends_on:
- db
networks:
- frontend
- backend
db:
image: postgres:16
environment:
- POSTGRES_PASSWORD=secret
- POSTGRES_DB=myapp
volumes:
- pgdata:/var/lib/postgresql/data
networks:
- backend
volumes:
pgdata:
networks:
frontend:
backend:
Compose File Versions
Docker Compose V2 (current) uses the services top-level key:
# V2 syntax (current)
services:
web:
image: nginx
# V1 syntax (deprecated)
version: "3.8" # No longer needed in Compose V2
web:
image: nginx
Basic Commands
# Start all services
docker compose up
# Start in 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
# Follow logs
docker compose logs -f
# Rebuild images
docker compose up --build
# Scale a service
docker compose up --scale web=3
Defining Services
Defining Services
Image vs Build
services:
# Use pre-built image
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: secret
# Build from Dockerfile
api:
build: ./api
# Or with options:
build:
context: ./api
dockerfile: Dockerfile.dev
args:
NODE_ENV: development
cache_from:
- myregistry.com/api:latest
# Build with target stage
worker:
build:
context: .
dockerfile: Dockerfile
target: builder
Ports
services:
web:
image: nginx
ports:
- "80:80" # host:container
- "443:443"
- "127.0.0.1:8080:80" # bind to localhost
- "3000" # random host port
Volumes
services:
db:
image: postgres:16
volumes:
- pgdata:/var/lib/postgresql/data # named volume
- ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro # bind mount
app:
image: node:20
volumes:
- .:/app # bind mount current dir
- node_modules:/app/node_modules # named volume for deps
volumes:
pgdata:
node_modules:
Environment Variables
services:
api:
image: myapi
environment:
- DB_HOST=db
- DB_PORT=5432
env_file:
- .env
- ./api/.env
# Or map to host env vars:
environment:
DB_HOST: db
DB_PASSWORD: ${DB_PASSWORD}
Complete Service Example
services:
web:
build:
context: ./frontend
dockerfile: Dockerfile
ports:
- "3000:3000"
volumes:
- ./frontend:/app
- /app/node_modules
environment:
- REACT_APP_API_URL=http://localhost:4000
depends_on:
- api
networks:
- frontend
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000"]
interval: 30s
timeout: 10s
retries: 3
Restart Policies
services:
api:
restart: no # Never restart (default)
restart: always # Always restart
restart: unless-stopped # Restart unless explicitly stopped
restart: on-failure:5 # Restart on failure, max 5 times
Compose Networking
Compose Networking
Automatic Network Creation
Compose creates a default network for your application:
# Starting compose creates a network named <project>_default
docker compose up -d
# Check the network
docker network ls | grep myproject
# myproject_default bridge local
All services in the same compose file automatically communicate by service name:
services:
web:
image: nginx
# Can reach 'api' by name: http://api:4000
api:
image: myapi
# Can reach 'db' by name: postgresql://db:5432
db:
image: postgres:16
Custom Networks
services:
web:
networks:
- frontend
api:
networks:
- frontend
- backend
db:
networks:
- backend
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true # No internet access
Network Aliases
services:
primary-db:
image: postgres:16
networks:
app:
aliases:
- database
- postgres
Port Publishing
services:
web:
image: nginx
ports:
# Publish to all interfaces
- "80:80"
# Publish to localhost only
- "127.0.0.1:443:443"
# UDP ports
- "53:53/udp"
# Host port range
- "8000-8010:8000-8010"
Project Name
# Default: directory name
docker compose -p myproject up -d
# All resources get prefixed
# Network: myproject_default
# Volumes: myproject_pgdata
# Containers: myproject_web_1, myproject_api_1