Learn / Programming / Docker / Docker Compose

Intermediate 15 min

Docker Compose

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

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.

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:
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.

  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.

Compose is not Kubernetes

Compose is perfect for development and small single-server deployments. For large multi-server production systems teams typically move to an orchestrator such as Kubernetes.

Try it yourself

Write a compose file with a web service using the nginx image on port 8080, and a redis service. Bring it up and check both are running.

Show solution
services:
  web:
    image: nginx:alpine
    ports:
      - "8080:80"
  redis:
    image: redis:7
# docker compose up -d && docker compose ps