Back
2/3

Multi-Service Setup

+20 XP on completion

#Multi-Service Setup

After this lesson you'll know:

  • how to orchestrate app + DB + Redis with Compose
  • what depends_on does (and doesn't do)
  • Build vs Image — the difference
  • how healthchecks work in Compose

#App + DB + Redis — the standard setup

services:
  api:
    build: .
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgresql://user:pass@db:5432/myapp
      REDIS_URL: redis://cache:6379
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started

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

  cache:
    image: redis:7-alpine

volumes:
  pgdata:

#build: vs image:

  • image: nginx — uses the image from Docker Hub
  • build: . — builds the local Dockerfile
  • build: ./my-folder — builds a Dockerfile in a subfolder

#depends_on & Healthchecks — the right way

depends_on starts services in the correct order. But without extra config, it does NOT wait until a service is ready.

Postgres takes 10-20 seconds on first start to initialize database files. Without a healthcheck, your app starts, connects — and gets connection refused.

The solution: A healthcheck periodically tests if the service is really ready. depends_on with condition: service_healthy then waits exactly as long as needed.

db:
  healthcheck:
    test: ["CMD-SHELL", "pg_isready -U user -d myapp"]
    interval: 5s
    timeout: 5s
    retries: 5
    start_period: 10s
  • test: The command that checks if the service is alive
  • interval: How often to check (every 5s)
  • timeout: Max time for one check (5s)
  • retries: How many failures to tolerate
  • start_period: Wait time before the first check — gives the service startup time

#docker compose watch — Hot Reload for Development

Since Docker Compose v2.22, there's docker compose watch — your code in the container updates automatically when you edit a file.

services:
  api:
    build: .
    ports:
      - "3000:3000"
    develop:
      watch:
        - action: sync
          path: ./src
          target: /app/src
        - action: rebuild
          path: ./package.json
docker compose watch
# Changes in ./src are synced live into the container
# Changes to package.json trigger a full rebuild

No more manual restarting. Compose detects action: sync for file changes and action: rebuild for dependencies.

Actions explained:

  • sync — copies changed files into the running container (fast)
  • rebuild — rebuilds the image and restarts the container (for dependencies)
  • restart — restarts the container without build (for config changes)

#Environment variables from .env

# .env file (automatically loaded by compose)
POSTGRES_PASSWORD=***
DATABASE_URL=postgresql://user:pass@db:5432/myapp
services:
  api:
    environment:
      DATABASE_URL: ${DATABASE_URL}
# Alternative: env_file
services:
  api:
    env_file: .env.production

#✋ Try it out

  • Create a compose.yml with Postgres + Redis + Healthcheck, start it and see how docker compose ps shows healthy
  • Add develop:

watch:

- action: sync

path: ./src

target: /app/src to your api service. Start with docker compose watch

  • docker compose logs -f api — see only one service's logs

#📌 Summary

  • build: . builds the local Dockerfile, image: ... pulls from Docker Hub
  • depends_on with condition: service_healthy waits until the service is ready
  • Healthchecks are the robust way to control startup order
  • docker compose watch syncs code changes live into the container
← → to navigate