Assignment 2: Compose a multi-service stack
Goal: Define and run a multi-service application with Docker Compose — your app from Assignment 1 plus a database — wired together on a user-defined network, with persistent data and proper readiness ordering, all from a single compose.yaml.
Where: Any lab VM with Docker installed. Your app image should be the one you pushed to the lab registry at 10.100.100.6 in Assignment 1.
Tasks
- Extend (or reuse) your app so it connects to a database (e.g. Postgres) using a connection string read from an environment variable like
DATABASE_URL, and reaches the DB by the service name (e.g.db), not an IP. - Create a
compose.yamlwith two services:db: an official database image, pinned to a version tag, with a named volume mounted at its data directory so data survives restarts.api: your image pulled from10.100.100.6/<yourname>/<app>:sha-<shortsha>, publishing its port to the host withports:.
- Put both services on a user-defined network (or rely on Compose's default network) so the
apiresolvesdbby name. - Add a
HEALTHCHECKto thedbservice (e.g.pg_isready) and makeapiusedepends_on:withcondition: service_healthyso it starts only after the DB is ready. - Keep secrets out of the committed file: put the DB password in an
.envfile (and add.envto.gitignore/.dockerignore). - Bring it up:
docker compose up -d. Verify withdocker compose psthat both services are running anddbishealthy. - Confirm the app can talk to the database (hit an endpoint that reads/writes the DB, or
docker compose logs apishowing a successful connection). - Test persistence: write some data,
docker compose down(WITHOUT-v),docker compose up -dagain, and confirm the data is still there. Then explain (in your writeup) whatdocker compose down -vwould have done differently.
Deliverable
Your compose.yaml, your .env.example (with the password value as <REDACTED>), and a short writeup showing: the docker compose ps output with db healthy, evidence the api reached db by name, and your persistence test result (data survived a down/up cycle).
Acceptance criteria — you're done when:
- A single
compose.yamldefines both thedbandapiservices. - The
apireaches the database by service name (e.g.db:5432), with no hard-coded container IP. - Both image references are pinned (db version tag; api is your
sha-tagged image from10.100.100.6). - A named volume persists the database data directory.
- The
dbservice has a workingHEALTHCHECKanddocker compose psshows it ashealthy. -
apiusesdepends_onwithcondition: service_healthy. - The DB password is in an
.envfile, not hard-coded incompose.yaml, and.envis git-ignored. -
docker compose up -dbrings the whole stack up; the app successfully reads/writes the database. - Data survives a
docker compose downfollowed bydocker compose up -d.
Hints
- Re-read Chapter 4 section 5 for the
depends_on+condition: service_healthypattern — plaindepends_ononly waits for start, not readiness. - Inside the
apicontainer you can test name resolution:docker compose exec api sh, thenping dborwget -qO- http://db:5432. docker compose configprints the fully resolved file — use it to catch YAML indentation errors before running.- If data vanished after
down, you probably randown -v(which deletes named volumes) or used a bind mount you cleaned up — check yourvolumes:block. - If
apicannot pull, re-rundocker login 10.100.100.6and double-check the image tag matches Assignment 1. - Even with
service_healthy, have your app retry its DB connection — that is realistic and avoids race conditions.
Blocked for >~30 min after re-reading the lessons? Bring what you've tried to your mentor.
No comments to display
No comments to display