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 YAMLHealth 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
