iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

compose.yaml

docker-compose.yml describes a multi-container app declaratively: services, networks, volumes, depends_on, healthchecks, profiles. Compose v2 (built into Docker Desktop and the CLI plugin) is the modern target — the v1 standalone tool is deprecated. The same file shape works for local dev, CI, and small single-host deploys.

A production-shaped compose file with health and profiles

EXAMPLE
# compose.yaml (Compose v2 — no 'version:' key needed)
name: shop

services:
  api:
    image: ${IMAGE:-shop/api:dev}
    build:
      context: ./api
      target: ${BUILD_TARGET:-runtime}
      cache_from: ['shop/api:latest']
    ports: ['3000:3000']
    environment:
      NODE_ENV: ${NODE_ENV:-development}
      DATABASE_URL: postgres://shop:${DB_PASSWORD}@db:5432/shop
      REDIS_URL:    redis://redis:6379
    depends_on:
      db:    { condition: service_healthy }
      redis: { condition: service_healthy }
    healthcheck:
      test: ['CMD', 'curl', '-fsS', 'http://localhost:3000/health']
      interval: 10s
      timeout: 2s
      retries: 5
      start_period: 30s
    restart: unless-stopped
    deploy:
      resources:
        limits:    { cpus: '1.0', memory: 768M }
        reservations: { cpus: '0.25', memory: 256M }
    logging:
      driver: 'json-file'
      options: { max-size: '20m', max-file: '5' }
    networks: [internal, web]

  worker:
    image: ${IMAGE:-shop/api:dev}
    command: ['node', 'dist/worker.js']
    environment:
      DATABASE_URL: postgres://shop:${DB_PASSWORD}@db:5432/shop
      REDIS_URL:    redis://redis:6379
    depends_on: { db: { condition: service_healthy }, redis: { condition: service_healthy } }
    restart: unless-stopped
    networks: [internal]
    profiles: ['workers']     # only started when 'docker compose --profile workers up'

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB:       shop
      POSTGRES_USER:     shop
      POSTGRES_PASSWORD: ${DB_PASSWORD:?required}
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ['CMD-SHELL', 'pg_isready -U shop -d shop']
      interval: 5s
      timeout: 2s
      retries: 10
    networks: [internal]

  redis:
    image: redis:7-alpine
    command: ['redis-server', '--save', '60', '1', '--loglevel', 'warning']
    volumes: [redisdata:/data]
    healthcheck:
      test: ['CMD', 'redis-cli', 'ping']
    networks: [internal]

  proxy:
    image: nginx:1.27-alpine
    ports: ['80:80']
    volumes: ['./nginx.conf:/etc/nginx/conf.d/default.conf:ro']
    depends_on: { api: { condition: service_healthy } }
    networks: [web]

networks:
  web:      {}                  # public-ish: exposed via 'proxy'
  internal: { internal: true }  # no path to public internet

volumes:
  pgdata:   {}
  redisdata: {}

# ----------------- Usage -----------------
# docker compose up -d                       # start the default services
# docker compose --profile workers up -d     # also start 'worker'
# docker compose ps                          # what's running + healthcheck status
# docker compose logs -f api                 # tail
# docker compose exec api sh                 # shell into a container
# docker compose down                        # stop + remove (KEEPS named volumes)
# docker compose down -v                     # ALSO removes volumes (dangerous)

# Multiple files (override patterns)
# docker compose -f compose.yaml -f compose.prod.yaml up -d
# Files merge in order; later wins. Standard pattern: base compose + dev/prod override.

# .env file is auto-loaded; use ${VAR:?required} to fail fast when missing.
# Resolve interpolation: docker compose config

Why it matters

Use `depends_on` with `condition: service_healthy` instead of bare ordering. Containers start in dependency order, but only the healthcheck tells Compose when the dependency is actually ready to serve — the difference between "the API starts and crashes because Postgres has not finished initialising" and "the API starts only after Postgres is accepting connections".

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
services:
    api:
        build: .
        ports: ["3000:3000"]
        depends_on: [db]
    db:
        image: postgres:16
        environment:
            POSTGRES_PASSWORD: secret
        volumes:
            - pgdata:/var/lib/postgresql/data
volumes:
    pgdata:
Try it Yourself »

Test yourself

Q1. Compose file name is conventionally…
Q2. Map a host port with…
Q3. Persist DB data with…

Discussion

Loading…