🛠️ Lab 5 — Containerize Your Capstone Core Service (50 pts)

Goal: Wrap your capstone's core service in FastAPI, containerize it with a secure multi-stage Dockerfile using uv, and bring up your full local stack — API plus datastore — with a single docker compose up command. Everything in this lab runs locally and is free. Steps:

  1. Expose your core logic through FastAPI. If you haven't already, wrap your capstone's model, pipeline, or agent in a FastAPI app with at least one /predict (or equivalent) endpoint and a /health endpoint. Follow the official FastAPI in Containers guide for the recommended app layout.
  2. Lock your dependencies with uv. Initialize (or confirm) a pyproject.toml for your service and run uv lock to generate a uv.lock. Commit both files — the lockfile is what makes your container reproducible.
  3. Write a multi-stage Dockerfile. Use a builder stage on a pinned base image (e.g. python:3.12-slim, never :latest) that copies in pyproject.toml + uv.lock and runs uv sync --frozen. Use a second, minimal runtime stage that copies only the installed environment and your application code from the builder stage — no compilers, no source of unrelated tooling. Reference the Docker Python containerization guide and the Machine Learning Mastery FastAPI + Docker guide for structure.
  4. Harden the image. In the runtime stage, create a dedicated non-root user (e.g. RUN useradd -m appuser) and add USER appuser before your CMD. Confirm your CMD launches Uvicorn on 0.0.0.0 at a fixed port. Never bake secrets or environment-specific config into the image.
  5. Add a datastore to the stack. Identify what your capstone needs locally — Postgres, Redis, a vector store, SQLite-backed volume, etc. — and add it as a second service in a docker-compose.yml alongside your API service.
  6. Write the docker-compose.yml. Define both services, map the API's port to the host, pass any runtime configuration (database URL, etc.) via environment: or an env_file: — never hardcoded into the Dockerfile — and add a named volume for the datastore so data survives restarts. Use the Docker multi-stage builds reference if you need a refresher on stage syntax, or Docker 101 if you want a broader Docker refresher first.
  7. Test the full stack. Run docker compose up from a clean checkout and confirm both containers start, the API container is running as a non-root user (docker exec <container> whoami), and your /predict endpoint returns a correct response when called against the running stack (e.g. via curl or FastAPI's interactive docs at /docs).
  8. Record the before/after. Note your final image size (docker images) and briefly describe what you removed from the runtime stage compared to a naive single-stage build.

Deliverables: Your Dockerfile, docker-compose.yml, pyproject.toml, and uv.lock, submitted as an upload (zipped project folder or repo link) plus a short text entry confirming docker compose up successfully launches your containerized core service with a working /predict endpoint, including your final image size and non-root user confirmation.