Lesson 03 Multi-Service Builds
Theme:
Lesson 03 · Slide 1

Lesson 03 — Multi-Service Builds

From one image to a running system. docker-compose, env config, shared networks.

Cloud & DevOps Module 1

Lesson 03 · Slide 2

The shift in mindset

Lessons 01–02: think about a single image

  • One Dockerfile
  • One docker build
  • One docker run

Lesson 03: think about a system

  • api + worker + database + cache
  • All talking to each other
  • Lifecycle managed together
  ┌─────────┐     ┌────────┐
  │   api   │────▶│postgres│
  └────┬────┘     └────────┘
       │
  ┌────▼────┐     ┌────────┐
  │ worker  │────▶│ redis  │
  └─────────┘     └────────┘
      
Lesson 03 · Slide 3

Why not just docker run four times?

Lesson 03 · Slide 4

docker-compose: one YAML for the whole stack

services:
  api:
    build: ./api
    ports: ["8000:8000"]
    env_file: ./.env
    depends_on: [postgres]
  worker:
    build: ./worker
    env_file: ./.env
    depends_on: [postgres, redis]
  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes: ["pgdata:/var/lib/postgresql/data"]
  redis:
    image: redis:7-alpine
volumes:
  pgdata:
Lesson 03 · Slide 5

The implicit network

http://api:8000/health works from inside any container in the stack.

Lesson 03 · Slide 6

Env files vs environment: keys

env_file: ./.env

  • Load vars from a file
  • Best for shared values: DB URLs, API keys for the same environment
  • The .env file is in .gitignore
  • Works across all services that share the file

environment: KEY: ${KEY}

  • Read from the shell where compose starts
  • Best for per-developer overrides
  • Overrides env_file when both define the same key
  • Great for CI pipelines (set vars in the pipeline, not in files)
Lesson 03 · Slide 7

The .env file pattern

A file literally named .env in the same directory as docker-compose.yml. Compose reads it automatically.

POSTGRES_PASSWORD=devpassword
DATABASE_URL=postgresql://urls:devpassword@postgres:5432/urls
REDIS_URL=redis://redis:6379/0
BASE_URL=http://localhost:8000
Critical: the literal file .env is in .gitignore. There is a separate .env.example (committed) that documents the required vars without their secret values.
Lesson 03 · Slide 8

depends_on is weak

The best answer is application-level retry — it works everywhere, not just in compose.

Lesson 03 · Slide 9

Healthchecks

postgres:
  image: postgres:16-alpine
  healthcheck:
    test: ["CMD-SHELL", "pg_isready -U postgres"]
    interval: 5s
    timeout: 3s
    retries: 5
api:
  depends_on:
    postgres:
      condition: service_healthy
Lesson 03 · Slide 10

Volumes: named vs bind mounts

KindSyntaxUse for
Named volumepgdata:/var/lib/...DB data, anonymous persistent storage
Bind mount./api:/appDev: live code reload from host
Lesson 03 · Slide 11

Hot-reload for development

api:
  build: ./api
  volumes: ["./api:/app"]   # bind-mount source
  command: ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]

The production image stays immutable. This override is dev only — put it in docker-compose.override.yml (Homework Task 3).

Lesson 03 · Slide 12

docker compose up lifecycle

CommandWhat it does
upBuild (if needed) + start in foreground
up -dDetach (run in background)
up --buildForce rebuild before starting
logs -f apiFollow one service’s logs
exec api /bin/shShell into a running container
restart workerRestart one service
downStop + remove containers (volumes kept)
down -vAlso remove volumes
psList running services
Lesson 03 · Slide 13

What lands in sample-app/ after this lesson

After Lesson 03: cloud-devops/sample-app/docker-compose.yml exists; docker compose up brings api + worker + postgres + redis online with one command; .env.example documents required env vars; the API reads DATABASE_URL from the environment.

Files added

  • docker-compose.yml
  • .env.example

Files changed

  • api/app.py — reads DATABASE_URL from env (even if still using in-memory storage in Plan A)
Lesson 03 · Slide 14

Recap & what’s next

Coming up: Module 2 — GitHub Actions builds and pushes these images on every commit, with scanning and signing.
1 / ?