DK

Docker Compose

Docker · 12 entries

Define Services

syntax
# compose.yaml
services:
  <service_name>:
    image: <image>
    build: <context>
example
services:
  api:
    build: .
    ports:
      - "3000:3000"
    depends_on:
      - db
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: devpass

Note The file can be named compose.yaml (preferred), compose.yml, docker-compose.yaml, or docker-compose.yml. Compose V2 is the current standard — use 'docker compose' (no hyphen) instead of the legacy 'docker-compose' binary.

Volumes in Compose

syntax
services:
  db:
    volumes:
      - <name>:<container_path>
volumes:
  <name>:
example
services:
  db:
    image: postgres:16
    volumes:
      - pgdata:/var/lib/postgresql/data
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
volumes:
  pgdata:

Note Named volumes declared in the top-level volumes: key persist across restarts and compose down. Bind mounts (host paths) are useful for development but should not be used for database storage in production.

Networks in Compose

syntax
services:
  api:
    networks:
      - frontend
      - backend
networks:
  frontend:
  backend:
example
services:
  web:
    image: nginx:alpine
    networks:
      - frontend
  api:
    build: .
    networks:
      - frontend
      - backend
  db:
    image: postgres:16
    networks:
      - backend
networks:
  frontend:
  backend:

Note Compose creates a default network automatically, so all services can already talk to each other. Define custom networks when you want to isolate groups — here, web cannot reach db directly.

Port Mapping in Compose

syntax
services:
  api:
    ports:
      - "<host>:<container>"
example
services:
  api:
    ports:
      - "8080:3000"
      - "127.0.0.1:9229:9229"

Note Always quote port mappings in YAML — values like 80:80 can be misinterpreted as a base-60 number by the YAML parser. Binding debug ports to 127.0.0.1 prevents accidental exposure on the network.

Environment Variables in Compose

syntax
services:
  api:
    environment:
      - KEY=value
    env_file:
      - .env
example
services:
  api:
    environment:
      NODE_ENV: production
      DB_HOST: db
    env_file:
      - .env.local

Note Compose automatically reads a .env file in the same directory for variable substitution in the compose file itself (e.g., image: myapp:${VERSION}). The env_file key loads variables into the container's environment.

Service Dependencies

syntax
services:
  api:
    depends_on:
      <service>:
        condition: <condition>
example
services:
  api:
    depends_on:
      db:
        condition: service_healthy
  db:
    image: postgres:16
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 3s
      retries: 5

Note Plain depends_on only waits for the container to start, NOT for the application inside to be ready. Use condition: service_healthy with a healthcheck to wait until the database actually accepts connections.

Build Configuration

syntax
services:
  api:
    build:
      context: <path>
      dockerfile: <file>
      args:
        KEY: value
example
services:
  api:
    build:
      context: .
      dockerfile: docker/Dockerfile.prod
      args:
        NODE_VERSION: 20
    image: myapp:latest

Note Setting both build and image means Compose builds and tags the result with that image name. The context path is relative to the compose file location.

Profiles

syntax
services:
  debug-tools:
    profiles:
      - debug
example
services:
  api:
    build: .
  db:
    image: postgres:16
  pgadmin:
    image: dpage/pgadmin4
    profiles:
      - debug
    ports:
      - "5050:80"
# Run with: docker compose --profile debug up

Note Services with profiles are not started by default. They only launch when you explicitly activate the profile. Great for optional tools like database GUIs, debug utilities, or test runners.

Start & Stop the Stack

syntax
docker compose up [options]
docker compose down [options]
example
docker compose up -d
docker compose up -d --build
docker compose down
docker compose down -v
output
✔ Container myproject-db-1    Started
✔ Container myproject-api-1   Started

Note up -d runs in the background. Add --build to rebuild images before starting. down stops and removes containers and default networks. down -v ALSO removes named volumes — this destroys your database data permanently.

Logs, Exec & Status

syntax
docker compose logs [service]
docker compose exec <service> <command>
docker compose ps
example
docker compose logs -f api
docker compose exec db psql -U postgres
docker compose ps
output
NAME              STATUS          PORTS
myproject-api-1   Up 5 minutes    0.0.0.0:3000->3000/tcp
myproject-db-1    Up 5 minutes    5432/tcp

Note logs -f follows all services by default — pass a service name to filter. exec opens a shell or runs a command inside a running service container.

Compose Watch (Hot Reload)

syntax
docker compose watch
# or in compose.yaml:
services:
  api:
    develop:
      watch:
        - action: sync
          path: ./src
          target: /app/src
example
services:
  api:
    build: .
    develop:
      watch:
        - action: sync
          path: ./src
          target: /app/src
        - action: rebuild
          path: package.json
# Then: docker compose watch

Note sync copies changed files into the container without rebuilding. rebuild triggers a full image rebuild when the specified file changes (e.g., when you add a dependency). This replaces many custom file-watching setups.

Override Files

syntax
# compose.yaml — base config
# compose.override.yaml — auto-loaded overrides
# docker compose -f compose.yaml -f compose.prod.yaml up
example
# compose.yaml
services:
  api:
    build: .
    ports:
      - "3000:3000"

# compose.override.yaml (auto-loaded in dev)
services:
  api:
    volumes:
      - ./src:/app/src
    environment:
      DEBUG: "true"

Note Compose automatically merges compose.override.yaml on top of compose.yaml. Use -f flags for other environments: docker compose -f compose.yaml -f compose.prod.yaml up. This keeps your base config DRY.