Skip to content

Docker Compose describes a multi-container application in one YAML file and runs it with a single command. Ideal for local development, testing and small single-host deployments. Base concepts are in 1. Docker.


Why Compose

  • One file defines services, networks and volumes
  • docker compose up replaces many long docker run commands
  • Reproducible dev environments that live in Git
  • Compose creates a default network where services reach each other by service name

Compose is the V2 plugin: use docker compose (with a space), not the legacy docker-compose.


Example: Web + Database + Cache

# compose.yaml
services:
  web:
    build: .                      # build from ./Dockerfile
    ports:
      - "8000:8000"
    environment:
      DATABASE_URL: postgres://app:${DB_PASSWORD}@db:5432/appdb
      REDIS_URL: redis://cache:6379
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started
    volumes:
      - ./src:/app/src            # bind mount for live code reload
    restart: unless-stopped

  db:
    image: postgres:16
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: appdb
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d appdb"]
      interval: 5s
      timeout: 3s
      retries: 10

  cache:
    image: redis:7-alpine

volumes:
  pgdata:

Notes: - web reaches Postgres at host db, Redis at cache (service names are DNS names) - depends_on with condition: service_healthy waits for the healthcheck, plain depends_on only orders start, not readiness - Only web publishes a port; db and cache stay private to the Compose network - ${DB_PASSWORD} is read from the shell or a .env file next to compose.yaml (keep .env out of Git)


Commands

$ docker compose up -d              # create and start in background
$ docker compose up --build         # rebuild images first
$ docker compose ps                 # status of services
$ docker compose logs -f web        # follow logs for one service
$ docker compose exec web sh        # shell in a running service
$ docker compose run --rm web pytest   # one-off container for a task
$ docker compose stop               # stop, keep containers
$ docker compose down               # stop and remove containers + network
$ docker compose down -v            # ...and remove named volumes (deletes data!)
$ docker compose config             # validate and print resolved config
$ docker compose pull               # pull newer images

Service Options

Key Purpose
image / build Use an image, or build from a context + Dockerfile
ports "host:container" publish
expose Container-only port (documentation / inter-service)
environment / env_file Environment variables
volumes Named volumes and bind mounts
networks Attach to specific networks
depends_on Start order, optionally wait for health
healthcheck Define readiness
restart no, always, on-failure, unless-stopped
command / entrypoint Override image defaults
deploy.resources.limits CPU / memory limits
profiles Optional services enabled on demand
secrets / configs Mount sensitive or config files

Build options

services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
      target: dev                 # multi-stage target
      args:
        VERSION: "1.0"

Networks

services:
  web:
    networks: [frontend, backend]
  db:
    networks: [backend]            # not reachable from frontend-only services

networks:
  frontend:
  backend:

Profiles (optional services)

services:
  adminer:
    image: adminer
    profiles: ["debug"]
    ports: ["8080:8080"]
$ docker compose --profile debug up -d

Configuration Management

Environment variable precedence (highest first)

  1. docker compose run -e
  2. Shell environment
  3. --env-file
  4. .env file
  5. environment: / env_file: in the Compose file
  6. ENV in the image

Multiple files for environments

Base file plus overrides, merged in order.

compose.yaml            # shared
compose.override.yaml   # auto-loaded in dev (bind mounts, debug)
compose.prod.yaml       # production tweaks
$ docker compose -f compose.yaml -f compose.prod.yaml up -d

Secrets

services:
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password

secrets:
  db_password:
    file: ./secrets/db_password.txt

Development Workflow

  • Bind-mount source code for hot reload, named volumes for databases and node_modules
  • docker compose watch (with develop.watch) syncs or rebuilds on file changes
  • Use a healthcheck on every dependency
  • Seed data by mounting scripts into /docker-entrypoint-initdb.d (Postgres / MySQL images)
  • Run tests in CI: docker compose up -d --wait, test, then docker compose down -v (see CI-CD Pipelines)

Compose vs Kubernetes

Compose Kubernetes
Scope One host Cluster of nodes
Scaling --scale web=3 manually Autoscaling, self-healing
Rollouts Recreate Rolling updates, rollbacks
Complexity Low High
Best for Dev, CI, small apps Production at scale

A Compose file can be translated into Kubernetes manifests with tools like Kompose. See 4. Running Your Containers in Kubernetes.