Skip to main content
Docker intermediate Lesson 6 of 9

Docker Compose

Define and run multi-container applications with Docker Compose. Services, networks, volumes, and environment configuration.

What Is Docker Compose?

Docker Compose lets you define a multi-container application in a single docker-compose.yml file and manage it with one command. Instead of running multiple docker run commands with flags, you declare all services, their configuration, networks, and volumes in one place.

docker-compose.yml Structure

services:           # containers to run
  web:
    image: nginx
    ports:
      - "8080:80"

  api:
    build: ./api    # build from a local Dockerfile
    environment:
      - NODE_ENV=production
    depends_on:
      - db

  db:
    image: postgres:15
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: secret

volumes:            # named volumes
  pgdata:

networks:           # custom networks (optional — Compose creates a default)
  backend:

Core Commands

# Start all services (build if needed), detached
docker compose up -d

# Start and force rebuild images
docker compose up -d --build

# Stop and remove containers (keeps volumes)
docker compose down

# Stop and remove containers AND volumes
docker compose down -v

# View logs for all services
docker compose logs -f

# View logs for one service
docker compose logs -f api

# List running services
docker compose ps

# Scale a service to N replicas
docker compose up -d --scale worker=3

Full Example: Node API + PostgreSQL + Redis

# docker-compose.yml
services:
  api:
    build: .
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgresql://postgres:secret@db:5432/myapp
      REDIS_URL: redis://cache:6379
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started
    volumes:
      - ./src:/app/src          # live reload in development

  db:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: secret
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5

  cache:
    image: redis:7-alpine
    volumes:
      - redisdata:/data

volumes:
  pgdata:
  redisdata:

Environment Variables

Inline in docker-compose.yml

environment:
  - NODE_ENV=production
  - PORT=3000

From a .env file (default)

Compose automatically loads .env in the same directory:

# .env
DATABASE_URL=postgresql://postgres:secret@db:5432/myapp
NODE_ENV=development
environment:
  - DATABASE_URL=${DATABASE_URL}
  - NODE_ENV=${NODE_ENV}

Multiple env files

docker compose --env-file .env.staging up -d

Service Dependencies and Health Checks

services:
  api:
    depends_on:
      db:
        condition: service_healthy   # wait until healthy

  db:
    image: postgres:15
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 10s

Without condition: service_healthy, depends_on only waits for the container to start — not for the service inside it to be ready.

Running Commands

# Run a command in a running service
docker compose exec api node --version

# Run a one-off command in a new container
docker compose run --rm api npm run migrate

# Open a shell
docker compose exec db psql -U postgres

Overriding with Multiple Files

# docker-compose.yml         — base config (shared)
# docker-compose.override.yml — dev overrides (applied automatically)
# docker-compose.prod.yml    — production overrides

# Development (uses base + override automatically)
docker compose up -d

# Production
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

docker-compose.override.yml is merged automatically in development. Keep dev-only settings (bind mounts, debug ports, hot reload) there.

Frequently Asked Questions

What version of Docker Compose should I use?
Use the Compose V2 plugin (docker compose, not docker-compose). It ships with Docker Desktop and Docker Engine 23+. The standalone docker-compose binary is deprecated.
Do I need to define networks in docker-compose.yml?
No. Compose creates a default network automatically and all services join it. Define custom networks only when you need isolation between groups of services.
How do I run a one-off command against a service?
Use docker compose run --rm service-name command. This starts a fresh container for the service and removes it on exit.