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.