Skip to content

Docker Compose

  • Docker Compose is a tool for defining and running multi-container applications using a single docker-compose.yml file.
  • Instead of running multiple docker run commands with flags, you declare your entire application stack (services, networks, volumes) in YAML and manage it with a single docker compose up.
  • Compose is now part of Docker CLI (docker compose - v2). The standalone docker-compose CLI (v1) is deprecated.
  • Service: A container definition. Each service maps to one Docker image and runs one or more container instances.
  • Network: Compose creates a default bridge network for the project. All services on it can reach each other by service name (built-in DNS).
  • Volume: Named storage attached to one or more services for data persistence.
  • Project: The logical grouping of everything in a docker-compose.yml. Project name defaults to the directory name.
# docker-compose.yml — a full application stack example
services:
web:
image: nginx:alpine
ports:
- "80:80" # host:container
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro # bind mount, read-only
depends_on:
- app # starts after app is healthy
app:
build:
context: . # build from Dockerfile in current dir
dockerfile: Dockerfile.prod
environment:
- DATABASE_URL=postgres://user:pass@db:5432/mydb # ⚠️ Don't hardcode creds — use env_file or secrets
env_file:
- .env # load additional env vars from file (add to .gitignore!)
restart: unless-stopped
db:
image: postgres:16-alpine
volumes:
- db-data:/var/lib/postgresql/data # named volume for persistence
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: mydb
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d mydb"]
interval: 5s
timeout: 5s
retries: 5
volumes:
db-data: # declares the named volume
Terminal window
# Start all services (detached)
docker compose up -d
# Start and rebuild images before starting
docker compose up -d --build
# Stop services (containers remain, volumes intact)
docker compose stop
# Stop and remove containers and networks (volumes preserved by default)
docker compose down
# Stop, remove containers, networks, AND named volumes
docker compose down -v
# View running services
docker compose ps
# Tail logs from all services
docker compose logs -f
# Tail logs from a specific service
docker compose logs -f app
# Execute a command in a running service
docker compose exec app bash
docker compose exec db psql -U user -d mydb
# Scale a service to N instances
docker compose up -d --scale app=3
# Pull latest images for all services
docker compose pull
# Validate and view the merged config
docker compose config
  • Compose creates a default bridge network: <project-name>_default.
  • Services can reach each other by service name - no IP addresses needed.
# In the app service, connect to the database using the service name "db"
DATABASE_URL=postgres://user:pass@db:5432/mydb # "db" resolves to the db container's IP
  • Define custom networks for isolation between service groups:
services:
frontend:
networks: [public]
api:
networks: [public, private]
db:
networks: [private] # db not reachable from frontend
networks:
public:
private:

The version: key at the top of the file specifies the Compose file format.

[!note] The version field is deprecated as of Compose Specification (2021). Modern Docker Compose (v2) uses the Compose Spec and ignores this field. Omit it from new files.

FormatNotable Addition
v1No version key. Services linked with links:.
v2Added version: key, depends_on:, health checks, named volumes.
v3Added deploy: key for Swarm mode (replicas, resource limits).
Compose SpecUnified spec replacing versioned formats. version field optional/ignored.
# Modern Compose file - no version key needed
services:
web:
image: nginx
Terminal window
# docker-compose.yml — base config
# docker-compose.override.yml — auto-loaded on top (dev defaults)
# docker-compose.prod.yml — explicit for production
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

Without health checks, depends_on only waits for the container to start, not to be ready:

depends_on:
db:
condition: service_healthy # waits for healthcheck to pass
Terminal window
# Run database migrations before starting the app
docker compose run --rm app python manage.py migrate
# If migrations need to connect to an exposed port, add --service-ports
# (docker compose run doesn't publish ports by default)
docker compose run --rm --service-ports app python manage.py migrate

Added in Docker Compose 2.22 — automatically syncs or rebuilds when files change:

docker-compose.yml
services:
app:
build: .
develop:
watch:
- action: sync # copy changed files into the container without rebuild
path: ./src
target: /app/src
- action: rebuild # rebuild image when these files change
path: package.json
Terminal window
# Start services with file watching enabled
docker compose watch

Useful alternative to bind mounts for development — more explicit about what triggers a sync vs a full rebuild.