Day 148 ยท Shipping the whole kitchen

Docker Fundamentals

You will be able to
  • Explain the difference between an image and a container, and how layers cache
  • Write a correct Dockerfile for a Python FastAPI app with a fast, cache-friendly layer order
  • Configure ports, volumes, and environment/secrets for a containerized service
  • Debug inside a running container and read build output to fix a broken layer
Today's ~120 minutes
Spaced-rep warm-up: packaging + OS cards (D17/D39)10 min
ELI5 + tech read: images/containers/layers, the Dockerfile20 min
Guided: containerize a FastAPI app + debug from inside40 min
Practice: audit and fix a bad Dockerfile15 min
Project: containerize the capstone API25 min
Quiz + flashcards10 min

Builds on: Day 17 โ€” Packaging & environments ยท Day 39 โ€” Operating systems essentials ยท Day 41 โ€” HTTP & your first API

The analogy

"It works on my machine" is the oldest lie in software. Your laptop has a specific Python, specific installed libraries, a specific OS, environment variables you set months ago and forgot โ€” and the server has none of that. So the app that ran perfectly for you dies on deploy, and you spend a day discovering the server had Python 3.9 where you had 3.12.

A container is the fix: instead of shipping just the recipe and hoping the destination kitchen has the right pots, you ship the whole kitchen. A Docker image is a frozen snapshot of a complete filesystem โ€” a slim Linux, the exact Python, your code, every dependency at pinned versions โ€” built once from a recipe file called a Dockerfile. A container is a running copy of that image, an isolated process that behaves identically on your laptop, a teammate's Mac, and a cloud server, because it carries its whole world with it. Remember Day 39's processes and Day 17's virtual environments? A container is that idea taken all the way: not just isolated Python packages, but an isolated operating environment, reproducible byte-for-byte. Ship the kitchen, and "works on my machine" becomes "works on every machine."

Why this matters on the job

Every production AI system you will deploy ships as a container โ€” it is the universal unit of deployment across AWS, GCP, Azure, and every PaaS. Your docs-QA service has a nasty dependency footprint (a web framework, an LLM SDK, a vector store client, an embedding model) and "pip install and pray" on a fresh server is exactly where deploys die. Containerizing makes the environment a reviewed, versioned artifact โ€” the same discipline as pinning your model ID (Day 141), applied to the whole runtime. For an FDE dropping software into a customer's infrastructure (Day 170), a container is often the *only* thing they'll accept: one image, no host contamination, reproducible in their VPC. Interviewers now assume you can write a Dockerfile; it is table stakes.

Watch it happen

Shipping the whole kitchen โ€” an image builds layer by layer

step 1 / 5
bottom
FROM python:3.12-slim โ† top

A Dockerfile builds bottom-up. Layer 1: the base image โ€” a minimal OS + Python, identical on every machine.

Guided practice

guided 1

Containerize a minimal FastAPI app

22 min
  1. In a scratch folder, create a tiny app/main.py with a FastAPI app exposing GET /health returning {"ok": true} and a requirements.txt with fastapi and uvicorn[standard].
  2. Create the Dockerfile from the tech section and a .dockerignore.
  3. Build it. terminal: docker build -t docsqa:dev . โ€” watch each layer; note which are pulled from cache on a second build.
  4. Run it, publishing the port and passing an env var. terminal: docker run --rm -p 8000:8000 -e APP_ENV=dev docsqa:dev.
  5. From another terminal: terminal: curl localhost:8000/health โ€” you should get {"ok":true} from inside the container.
  6. Prove the cache rule: edit main.py (not requirements), rebuild, and confirm the pip layer is reused (CACHED) while only the code layer rebuilds.
๐Ÿ python โ€” editable, runs in your browser
Ctrl/โŒ˜+Enter runs ยท Tab indents ยท numpy/pandas/sklearn auto-load on import (torch and network calls need a local run)
guided 2

Break it, then debug from inside

18 min
  1. Deliberately introduce the classic bug: change the CMD to bind --host 127.0.0.1. Rebuild, run with -p 8000:8000, and confirm curl localhost:8000/health now hangs or refuses โ€” the app is only listening inside the container's loopback.
  2. Get a shell in the running container: terminal: docker exec -it $(docker ps -q -l) /bin/bash. Inside, run curl localhost:8000/health โ€” it works *inside* but not outside. That contrast is the whole lesson.
  3. Fix the host back to 0.0.0.0, rebuild, verify externally.
  4. Second bug: pass no APP_ENV and confirm the app reports "env":"unset" โ€” showing config comes from run-time env, not the image.
  5. Inspect the image layers: terminal: docker history docsqa:dev โ€” read the layer sizes and connect them to your Dockerfile lines.

On your own

Audit a bad Dockerfile

15 min

Here is a Dockerfile with at least five problems. Find them, explain the impact of each, and rewrite it correctly.

FROM python:3.12
COPY . .
RUN pip install -r requirements.txt
ENV ANTHROPIC_API_KEY=sk-ant-abc123
EXPOSE 8000
CMD uvicorn app.main:app --host 127.0.0.1 --port 8000

Goal: name each defect and its consequence (cache busting, image bloat, a secret baked into a layer forever, unreachable binding, shell-form CMD that swallows signals, no .dockerignore, running as root), then produce a corrected version.

Hints: which line reinstalls every dependency on every code change? Which line will leak a credential to anyone who pulls the image? Which line makes the app unreachable from the host?

Ship before you stop

Containerize the capstone API

Write a production-minded Dockerfile for your real docs-QA service and prove it runs identically in a container. Deliver: a Dockerfile (slim base, dependency layer before code, non-root user, exec-form CMD binding 0.0.0.0), a .dockerignore that excludes secrets, data, and logs, and a short docker/README.md documenting the build and run commands plus every environment variable the container needs. Persist the vector index and logs via a mounted volume so they survive docker rm. Verify: the container answers a real docs-QA query end to end (retrieval + generation) with only env-provided secrets, and a code-only edit rebuilds in seconds thanks to the cache order. Record the final image size and one thing you'd do to shrink it (foreshadowing Day 149's multi-stage builds).

Rubric โ€” check what you completed (0/6)

Common mistakes & misconceptions

  • Copying code before installing dependencies. Every code edit then busts the pip cache and reinstalls everything โ€” put COPY requirements.txt and pip install before COPY . .
  • Baking secrets into the image with ENV or COPY. Layers are permanent and readable by anyone with the image; pass secrets at run time via --env-file / -e.
  • Binding uvicorn to 127.0.0.1 inside the container. It then only listens on the container loopback and is unreachable from the host โ€” bind 0.0.0.0.
  • Using the full python:3.12 base and never cleaning apt lists. Images balloon to a gigabyte; use -slim and rm -rf /var/lib/apt/lists/* (multi-stage on Day 149 goes further).
  • Shell-form CMD (CMD uvicorn ...). It runs under /bin/sh which swallows SIGTERM, so the container won't shut down cleanly; use the JSON exec form.
  • No .dockerignore. The whole .git history, .env, and local data get copied into the build context and often the image โ€” slow, bloated, and a leak risk.
Knowledge check

Q1. What is the difference between a Docker image and a container?

Q2. Why does `COPY requirements.txt` and `pip install` belong before `COPY . .` in the Dockerfile?

Q3. Your containerized app returns healthy to `curl` run inside the container but refuses connections from the host. Most likely cause?

Go deeper โ€” curated resources

docsDocker โ€” Get Started โ†—30 mindocsDocker โ€” Dockerfile reference โ†—25 mindocsFastAPI โ€” Deployment in containers โ†—20 min
If you have a third hour
Done means
  • FastAPI app builds and runs in a container, reachable via curl on the host
  • Cache-order rule demonstrated (code edit reuses the pip layer)
  • 127.0.0.1 vs 0.0.0.0 bug reproduced and fixed from inside the container
  • Capstone Dockerfile + .dockerignore committed; container answers a real query with env-supplied secrets
  • Quiz โ‰ฅ 2/3
How this connects

โ† Back: This takes Day 17's virtual-environment idea all the way to a reproducible OS-level artifact, using Day 39's process/isolation model and serving the Day-41 FastAPI app you've grown into the capstone.

Forward โ†’: Day 149 composes this image with a vector DB and cache and shrinks it with multi-stage builds; Day 151 deploys it to the cloud; Day 152 builds and pushes it in CI, and Day 170 ships it into a customer's environment.

Unlocks: D149 Compose, Registries & Image Hygiene ยท D150 Cloud Fundamentals ยท D151 Deploy Lab โ€” Container to Cloud URL ยท D152 CI/CD with GitHub Actions