Learn / Programming / Docker / Docker Compose

Docker · Lesson 3 of 3

Docker Compose

Run an app with a database and other services from one file.

  • Intermediate
  • 15 min read
  • 3 objectives

Before this lessonLesson 2: Writing a Dockerfile

What you will learn

  • Write docker-compose.yml
  • Connect services by name
  • Persist data with volumes

Real applications are several services: a web app, a database, a cache. Starting each with a long docker run command is tedious and error-prone. Docker Compose describes the whole stack in one YAML file and starts everything with a single command.

A complete example

services:
  api:
    build: .
    ports:
      - "8000:8000"
    environment:
      DATABASE_URL: postgresql://app:secret@db:5432/appdb
      REDIS_URL: redis://cache:6379
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started

  db:
    image: postgres:16
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: appdb
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d appdb"]
      interval: 5s
      retries: 5

  cache:
    image: redis:7

volumes:
  pgdata:

Running it

docker compose up -d          # build if needed and start everything
docker compose ps
docker compose logs -f api
docker compose exec db psql -U app appdb
docker compose down             # stop and remove containers
docker compose down -v          # ...and delete volumes (data!)

Networking by service name

Compose puts all services on a private network where each is reachable by its service name. That is why the URL above uses db and cache as hostnames rather than localhost. Inside a container, localhost means the container itself.

Volumes and data

A named volume (pgdata) keeps the database files across restarts and rebuilds. Without it, your data disappears whenever the container is recreated.

Development conveniences

  api:
    build: .
    volumes:
      - ./:/app                 # live-reload: edit code on your machine
    command: uvicorn main:app --reload --host 0.0.0.0
    env_file:
      - .env                    # keep settings out of the YAML

Health checks and start order

depends_on alone only waits until the container starts, not until the database is ready to accept connections. Combining it with a healthcheck, as above, makes the app wait for a truly ready database.

# Write your solution here
Course completeYou finished DockerReview the full course or pick your next one.