sehaj/learn
Docker/06 Compose

06.02 · with code · 11 min

Multi-service apps

Describe the whole notes stack in one file: how services find each other, how networks keep them apart, and how to work with one service at a time.

The core idea

A multi-service app in Compose is the same set of containers you'd run by hand, but the relationships are written down: which services share a network, which volume holds the data, which service is built from source. Compose turns those relationships into the right docker calls, in the right order.

The notes stack as services

compose.yaml (trimmed)
name: notes

services:
proxy:
  image: nginx:alpine
  ports: ["8080:80"]
  volumes:
    - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
  networks: [frontend]

api:
  build:
    context: ./api
    target: dev
  environment:
    DATABASE_URL: postgres://notes:notes@db:5432/notes
    REDIS_URL: redis://cache:6379
  networks: [frontend, backend]

db:
  image: postgres:18
  volumes:
    - pgdata:/var/lib/postgresql
  networks: [backend]

cache:
  image: redis:8-alpine
  networks: [backend]

networks:
frontend:
backend:

volumes:
pgdata:

The full file, with healthchecks and startup order, is in project/docker/compose.yaml; the next two lessons fill in the missing parts.

Things worth noticing:

  • Service names are hostnames. db, cache and api resolve through Docker's DNS, exactly like container names did in 05.02 User-defined bridges and DNS. That's why DATABASE_URL says @db:5432, and why the Nginx config now proxies to http://api:3000.
  • Networks are the security boundary. api is on both; proxy can't see db or cache.
  • Only proxy has ports:. Services talk to each other over their networks without publishing anything.
  • Relative paths like ./nginx/default.conf and ./api are relative to the compose file, so the project works wherever it's cloned.
  • build: instead of image: for code you own. Compose builds it the first time, and again with docker compose up --build or docker compose build.

Working with one service

Most Compose commands take service names:

❯terminal
$ docker compose logs api --tail 3
api-1 | notes-api listening on :3000 (max note length 280)
$ docker compose exec db psql -U notes -c 'SELECT count(*) FROM notes'
count
-------
3
(1 row)
$ docker compose run --rm --no-deps cache redis-cli -h cache ping
PONG
  • exec runs a command in the service's running container, like docker exec.
  • run starts a new, one-off container from the service's definition, with its networks and environment. --rm removes it afterwards and --no-deps skips starting its dependencies. It's how you run migrations, scripts or a one-time shell.
  • restart api, stop cache, up -d proxy all work per service too.
Predict first

The api service connects to postgres://notes:notes@localhost:5432/notes. Every other service is healthy, but the API can't reach the database. Why?

Commit to an answer before you read on.
CheckpointBring up the stack

From project/docker (step 5, docker-step-05), run docker compose up -d. Use exec to count notes in Postgres, run --rm to ping Redis, and logs -f api while you curl -X POST localhost:8080/notes. Then docker compose stop cache and confirm the API still serves notes.

STUCK? ▸

proxy returns 502: the API is still starting, or isn't on frontend. docker compose logs api says which.

Recap

Interview questions

JuniorfreqMust-knowConcept

How do services in a Compose project communicate with each other?

Report a mistakeEdit this page
☰ Compose06.02 · 33/44next →