Docker from Basics to Production · Capstone
Capstone: Containerize and Ship a Three-Service App
Take a web frontend, an API and a background worker with PostgreSQL and Redis from 'works on my machine' to images in a registry and a production-like Compose setup.
Scenario
Example company: a startup's product has a React frontend (built to static files), a Python API, a Python background worker, PostgreSQL and Redis. Developers install everything locally with different versions; the production server is set up by hand. They want containers for development, CI and a first production deployment on a single VM with Compose.
Requirements
- One command starts the full stack for development, with live reload for the API.
- CI builds images on every merge, tests them, and pushes them with immutable tags.
- The production VM runs the exact images CI tested, restarts crashed services, and keeps database data safe.
Constraints
- The team has no Kubernetes experience; production is one VM for now.
- Secrets (database password, API keys) must never be in Git or images.
- The API must not drop requests during deploys.
Deliverables
- Dockerfiles for frontend, API and worker (stages, base images, users).
- Compose files for development and production (what differs and how).
- CI pipeline steps and tagging.
- Secrets and configuration handling.
- Deploys, restarts and data safety on the VM.
Hints
- The frontend's runtime is just static files.
- API and worker can share one image with different commands.
- Healthchecks matter for startup order and for zero-downtime deploys.
Draft each deliverable below before revealing the model answer and rubric.
Further reading: Dockerfile best practices · Compose in production
Deliverables
Describe the Dockerfiles.
Model answer
- Frontend: stage 1
node:24-alpinerunsnpm ciandnpm run build; stage 2nginx:1.29-alpine(or an unprivileged nginx variant) serves/usr/share/nginx/htmlwith a small config for SPA routing and caching headers. - API and worker: one image from
python:3.14-slim: dependency layer from a lockfile, then code, non-root user,PYTHONUNBUFFERED=1;CMDstarts the API (exec form, gunicorn or uvicorn), and the worker service overrides the command. .dockerignoreexcludes.git,node_modules,.env, tests artefacts; base images pinned and rebuilt weekly.
Rubric
Look for multi-stage frontend with a static server, a shared Python image with different commands, non-root users, pinned bases and .dockerignore.
Describe the Compose setup for development and production.
Model answer
compose.yaml(production-like): servicesweb,api,worker,db,cache; images by immutable tag; healthchecks on db, cache and api;depends_onwithservice_healthy; named volumes for PostgreSQL;restart: unless-stopped; onlywebpublishes a port (or a reverse proxy does).compose.override.yaml(development, loaded automatically):build:instead ofimage:, bind-mounted source with auto-reload, debug ports on 127.0.0.1,watchrules.- Production runs
docker compose -f compose.yaml up -dso the override is never used there.
Rubric
Strong answers separate production-like base from development overrides, use healthchecks and named volumes, and limit published ports.
Describe the CI pipeline.
Model answer
- On every merge: build each image with Buildx and registry or CI cache; tag with the Git SHA.
- Run unit tests inside the API image and an integration test with
docker compose up --waitand a smoke test. - Scan images; fail on critical vulnerabilities with available fixes.
- Push SHA tags; on release, add version tags; record digests in the deployment manifest (the production compose file references digests or SHA tags).
Rubric
Look for build once, test the shipped image, scan, immutable tags and recorded digests.
How are secrets and configuration handled?
Model answer
- Non-secret configuration via environment variables in the compose files.
- Secrets as Compose
secretsfiles on the VM (created from a secret manager by the deploy script, permissions 600), read from/run/secretsby the applications;.envfiles are git-ignored. - Build-time credentials (private package registry) through BuildKit secret mounts in CI.
Rubric
Strong answers keep secrets out of Git, images and build args, and deliver them as files at run time.
How do deploys, restarts and data safety work on the VM?
Model answer
- Deploy: pull the new tags,
docker compose up -dservice by service; for the API, run a second replica behind the reverse proxy and switch only after its healthcheck passes (or use a proxy that drains connections), and handle SIGTERM gracefully. restart: unless-stoppedon all services; log rotation configured in daemon.json.- PostgreSQL data on a named volume with nightly
pg_dumpto off-VM storage and tested restores; neverdown -vin production. - Migrations run as a one-off
docker compose run --rm api migratebefore switching traffic.
Rubric
Look for health-gated deploys with graceful shutdown, restart policies, log rotation, backups with restore tests and controlled migrations.
Open in the app to mark lessons complete, take notes and track your progress.