Run your app and its database together — with one command.
Module 7 · We give the notes app a real database. Free · No cloud account needed.
Beginner Containers ~50 minBy the end of this module you will be able to:
docker-compose.yml filedocker compose updepends_onPrerequisites: Module 6 — Docker. You should already be able to build an image and run a container.
In Module 6 you ran a single container. But real apps are rarely alone — a web app needs a database, maybe a cache, maybe a worker. Wiring those up by hand gets ugly fast:
You'd have to remember every flag, the right order, the network name — and type it all again after every reboot. Docker Compose replaces all of that with one file and one command.
Describe all your containers, their config, and how they connect in a single docker-compose.yml file. Then start the entire stack with docker compose up — and tear it down with docker compose down.
Modern Docker uses docker compose (space, built in). Older tutorials show docker-compose (hyphen, a separate tool). If the space version errors, try the hyphen one — they behave the same.
A Compose file is written in YAML — indentation matters (use spaces, never tabs). Four words cover almost everything:
| Key | What it defines |
|---|---|
services | Your containers. Each service = one container (web, db, …). |
volumes | Named storage that lives outside a container so data survives restarts. |
networks | How services reach each other. Compose makes one automatically — services find each other by name. |
environment | Config values (passwords, URLs) passed into a container. |
YAML uses 2 spaces per level and no tabs. A single misaligned line breaks the whole file. If you get a cryptic parse error, check your spacing first.
We'll upgrade the notes app from Module 6 so it actually saves notes in a PostgreSQL database — all orchestrated by Compose. Start in your notes-app folder from last module.
app.pyThe app now reads/writes notes in Postgres. Note how it reads the connection string from an environment variable — never hard-code secrets.
requirements.txtYour Dockerfile from Module 6 stays exactly the same — it already installs from requirements.txt. Compose will build it for you.
docker-compose.ymlCreate this file in the project root. This is the heart of the module — read every line, we explain them next.
@dbLook at the DATABASE_URL — the host is literally db, the name of the other service. Compose's built-in network lets containers reach each other by service name. No IP addresses, ever.
Compose builds your web image, pulls Postgres, creates the network and volume, and starts both containers in order. Watch the logs from both services stream together. Open http://localhost:8080, add a few notes. ✅
Two containers, a network, and persistent storage — all from one file and one command. That's the leap from Module 6.
Stop the stack, then bring it back — your notes are still there, because they live in the db-data volume, not the container.
down -v deletes datadocker compose down keeps named volumes. Adding -v (docker compose down -v) deletes the volume too — your notes are gone. Use it only when you want a clean slate.
That last command runs psql inside the database container and queries your notes directly — proof the web and db containers are truly talking.
| Key | What it does |
|---|---|
build: . | Build this service from the Dockerfile in the current folder (our web app). |
image: postgres:16 | Use a ready-made image from the registry instead of building (the database). |
ports: "8080:5000" | Map host port 8080 → container port 5000, same as -p in Module 6. |
environment | Inject config into the container — DB credentials, connection URL. |
depends_on: db | Start db before web (controls order, not readiness — see note). |
volumes: db-data:/var/lib/... | Mount the named volume into Postgres's data directory so data persists. |
volumes: (top level) | Declare the named volume db-data that Docker manages for you. |
depends_on doesn't wait for "ready"It waits for the db container to start, not for Postgres to be accepting connections. In production you add a healthcheck or retry logic. For this lab, Postgres starts fast enough; if you ever hit a connection error on first boot, just re-run docker compose up.
You'll meet both. Know the difference:
| Type | Syntax | Use it for |
|---|---|---|
| Named volume | db-data:/var/lib/... | Persistent data Docker manages (databases). What we used. |
| Bind mount | ./app:/app | Map a host folder into the container — great for live-editing code in dev. |
Add a bind mount to the web service (volumes: ["./:/app"]) and your code edits show up inside the container without rebuilding — a huge speed-up while developing.
The commands you'll use every day. Bookmark this.
| Command | What it does |
|---|---|
docker compose up | Build (if needed) and start all services |
docker compose up --build | Force a rebuild, then start |
docker compose up -d | Start in the background (detached) |
docker compose down | Stop & remove containers and network (keeps volumes) |
docker compose down -v | Same, but also delete named volumes (⚠ data loss) |
docker compose ps | List this project's containers |
docker compose logs -f | Follow logs from all services |
docker compose logs -f web | Follow logs from one service |
docker compose exec web bash | Open a shell inside a running service |
docker compose build | Build/rebuild images without starting |
docker compose restart web | Restart a single service |
docker compose stop | Stop without removing |
| Symptom | Likely cause & fix |
|---|---|
yaml: line N: ... parse error | Indentation — use 2 spaces, no tabs, consistent alignment. |
| web can't connect to db on first run | db not ready yet. Re-run docker compose up, or add a healthcheck. |
| Notes disappear after restart | You ran down -v, or the volume isn't mounted to the data path. |
| Code change not reflected | Rebuild: docker compose up --build (or add a bind mount for dev). |
port is already allocated | 8080 in use — change to "8081:5000". |
docker compose not found | Older install — use the hyphen form docker-compose. |
Make it yours before moving on:
.env file instead of hard-coding it.web so code edits apply without rebuilding.db a healthcheck so web waits until it's truly ready.Define a multi-container app in one file, run it with one command, persist data with volumes, and let services talk over a Compose network. This is how most teams run apps locally.
Next up: Module 8 — Kubernetes Intro, where you'll take this same app and orchestrate it on a real (local) cluster with Minikube — the step from one machine to many.