TicketFlow's image, grown up. No solutions.md before submitting.
Exercise 1 — The Dockerfile
- Write the complete multi-stage Dockerfile (builder + runtime, non-root user, healthcheck) and compare against your previous image:
docker images— how many MB did it save? - The non-root user: run
docker run --rm ticketflow whoami(expected: uid 10001) and demonstrate it CANNOT write to/usr(docker run --rm ticketflow touch /usr/x→ Permission denied). Why does this matter with 22 in hand? - Reproducibility: build twice and compare the image IDs. Change ONE code comment and rebuild: does the hash change? Which layer got invalidated and which survived (docker build with the cached-steps output)?
Exercise 2 — The secret and the CVE
- The secret test:
docker history ticketflow --no-trunc | grep -iE "secret|password|key"→ empty? And add the.dockerignore(.env*,.git,tests/,db.sqlite3). Break it on purpose: do aCOPY..WITHOUT the dockerignore with a.envpresent and find the secret withdocker export/docker history. Document the finding. - The scan:
trivy image ticketflow:latest(ordocker scout cves). List the base layer's top 3 CVEs. Which one is "real" (exploitable in your context) and which is noise? Write the triage criterion in 3 lines. - The pinned digest: change
python:3.12-slimtopython:3.12-slim@sha256:...(your current version's digest) and verify the build stays identical. Which disaster does pinning avoid?
Exercise 3 — The compose
- Assemble the complete compose (db, redis, web, worker, beat with healthchecks and
service_healthy) and bring it up:docker compose up— paste the boot log showing web WAITED for db's health. - Hot-reload: edit a view from your editor and verify runserver reloads inside the container (the bind mount). Then: edit
requirements.txtand rebuild: did the pip cache survive a code change? (the COPY order). - The worker on the same image:
docker compose run --rm worker shell— a shell inside the full environment (DB, Redis). What do you use it for day to day (24's shell without a request)?
Exercise 4 — Migrations at boot
- Break it on purpose:
command: sh -c "python manage.py migrate && runserver"and scaledocker compose up --scale web=3. Watch the 3 migrates compete (logs). Which lock saved it from corrupting (10), and what annoyance does it generate? - The grown-up pattern: remove migrate from web, run
docker compose run --rm web python manage.py migrateONCE (the deployment's job) and then scale to 3 replicas: clean boots with no migrate. - The clean-boot proof: kill web (docker compose restart web) and time until the first 200 on /healthz/. What does the boot wait for, and what doesn't it wait for?
Exercise 5 — The production image
- Size: list the top 5 directories with
docker export | du(or dive if you have it): what is heaviest and what would you shave? Is the effort worth it (distroless for real, or is slim enough)? - The E2E compose (35): reuse the compose with
command: pytest tests/e2eand a data seed: how long does the full environment take from zero (build+up+ready)? The metric deciding whether E2E runs on every PR (41). - Write the ADR (48) "TicketFlow's image": multi-stage slim, non-root user, pinned digest, config per environment. The 3 reasons and the discarded alternative (one giant image? a base VM?). 10 lines.
Submit
Paste the Dockerfile with its MB, the non-root and secret evidence, the service_healthy log and the image ADR. Next: Lesson 41 — CI/CD.