Workshops ... Part 10: Docker Compose

Part 10: Docker Compose

In Part 9: Postgres in a container we ran Postgres as one container and the app locally against it. That works, but it's two things to start in the right order, and the app isn't containerized alongside the database. Docker Compose describes the whole stack, the app image and Postgres, in one file, so a single command brings both up together.

Describe the stack

Ask the assistant for a Compose file with the two services wired together:

Write a docker-compose.yml with a postgres service and the app service built from
backend/Dockerfile. Give Postgres a named volume so data survives, add a
healthcheck, and make the app wait for Postgres to be healthy. Pass the app a
DATABASE_URL that points at the postgres service.

The result runs both containers and connects them over Compose's network:

services:
  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: ${POSTGRES_USER:-snakearena}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-snakearena}
      POSTGRES_DB: ${POSTGRES_DB:-snakearena}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U snakearena"]
      interval: 10s
      timeout: 5s
      retries: 5

  backend:
    build:
      context: .
      dockerfile: backend/Dockerfile
    environment:
      DATABASE_URL: postgresql://${POSTGRES_USER:-snakearena}:${POSTGRES_PASSWORD:-snakearena}@postgres:5432/${POSTGRES_DB:-snakearena}
    ports:
      - "80:8000"
    depends_on:
      postgres:
        condition: service_healthy

volumes:
  postgres_data:

Two details do the real work. The named postgres_data volume holds the database files outside the container, so docker compose down and back up keeps every account and score. And depends_on with condition: service_healthy holds the app back until the pg_isready healthcheck passes, so the backend doesn't try to connect before Postgres is ready.

Notice the database host is postgres, the service name. Compose gives each service a hostname on its network, so the app reaches the database at postgres:5432 without anyone needing an IP address.

Configure and run

Copy the example environment file:

cp .env.example .env

It holds the Postgres credentials and a SECRET_KEY for signing tokens. Add .env to .gitignore to keep it out of git, and commit .env.example as the template.

Set these values:

POSTGRES_USER=snakearena
POSTGRES_PASSWORD=snakearena
POSTGRES_DB=snakearena
SECRET_KEY=change-this-to-a-random-string
DEBUG=true

Bring the whole stack up with one command:

docker compose up --build

The app is at the app home page, which Compose maps to port 80, and the API docs sit alongside it. Stop the stack with docker compose down.

We run these two often, so we add them to the Makefile:

up:
    docker compose up --build

down:
    docker compose down

After that, make up brings the stack up and make down tears it down. We take this same app image to a real server in Part 11: Deploy to AWS with infrastructure as code.

Questions & Answers

Sign up to ask questions, track your progress, and get access to other workshops · Already have an account? Sign in