Practice brief
Docker Compose Exercises
Every docker run command you have typed since Module 8 — the network flags, the environment variables, the volume mount — belongs in a single file. In Module 11 you write that file: a docker-compose.yml that replaces all the manual commands and gives the entire TaskFlow setup a one-command start.
Build from scratch
Write the TaskFlow Compose File
Since Module 8 you have been starting the TaskFlow stack with a sequence of docker run commands: create the network, start the database, start the API. If you restart your machine or need to share the setup with a teammate, you have to remember the exact flags, the environment variables, and the order. In this exercise you will write a docker-compose.yml that encodes all of that configuration. After this, starting the entire TaskFlow stack is a single command.
Done when
Try it yourself first. Open the guided path if you get blocked.
Step 1 — Remove the existing containers and network
docker rm -f taskflow-api taskflow-db
docker network rm taskflow-backend taskflow-frontend Clean up the manually created containers and networks from earlier modules. Compose will create its own network automatically — you do not need taskflow-backend or taskflow-frontend to exist before running docker compose up.
Step 2 — Create the docker-compose.yml file
touch docker-compose.yml Create the file at the root of your taskflow-lab directory — the same level as your Dockerfile. Compose looks for docker-compose.yml (or compose.yml) in the current directory by default.
Step 3 — Write the Compose file
Open docker-compose.yml in your editor and write the following content. Each key maps directly to a flag you used in earlier modules. services: db: image: postgres:16-alpine environment: POSTGRES_PASSWORD: secret POSTGRES_DB: taskflow volumes: - taskflow-data:/var/lib/postgresql/data api: build: . ports: - "8000:8000" environment: DATABASE_URL: postgres://postgres:secret@db:5432/taskflow depends_on: - db volumes: taskflow-data: The DATABASE_URL hostname is db — the service name. Compose creates a network automatically and registers each service by its name as a hostname on that network. No --network flag needed.
Step 4 — Start the stack
docker compose up -d Compose builds the api image from your Dockerfile, pulls postgres:16-alpine, creates the network and volume, and starts both containers. The -d flag runs everything in the background. Watch the output — Compose shows which containers are starting and which images are being pulled or built.
Step 5 — Verify the stack is running
docker compose ps
curl http://localhost:8000/health docker compose ps shows the status of every service defined in the file. Both api and db should show as running. The curl confirms the API is reachable. If the API is not up yet, wait a few seconds and try again — the database may still be initialising.
Step 6 — Stop and remove everything with one command
docker compose down docker compose down stops and removes the containers and the network Compose created. The volume is not removed by default — your data survives. To also remove volumes run docker compose down --volumes. This is the equivalent of all the individual docker rm and docker network rm commands from earlier modules, in a single step.
Break-fix
The API That Starts Before the Database Is Ready
A teammate wrote a Compose file with depends_on: db. They expected the API to wait for the database to be ready before starting. Instead, the API starts, immediately tries to connect, gets connection refused, and crashes. depends_on controls start order, not readiness. The database container starts a few seconds before the API, but Postgres takes longer than that to finish initialising. Reproduce this, understand why depends_on alone is not enough, and add the condition that makes the API wait for Postgres to actually be ready.
Reproduce this
docker compose down --volumes
docker compose up -d
docker compose logs api You're done when
Show investigative hints
- Run docker compose logs api immediately after starting. You will see connection refused errors. The API started, tried to connect, and failed — because Postgres was still initialising even though the container was running.
- depends_on: db only means the db container was created before the api container. It does not mean Postgres finished starting up inside that container.
- Compose supports a condition: service_healthy on depends_on. For that to work, the db service needs a healthcheck defined that runs pg_isready. Look up the healthcheck key in the Compose reference.
Show diagnosis and fix rationale
depends_on without a condition controls container start order only. It does not wait for Postgres to accept connections. Add a db healthcheck that runs pg_isready, then update api depends_on to use condition: service_healthy. The API will start after the database reports healthy, not merely after the database container exists.