Docker and config: making "works on my machine" a guarantee
Your serving code depends on a Python version, a pile of packages, and system libraries. Docker freezes all of it into an image — a snapshot that runs identically on your laptop, CI, and the cloud. "Works on my machine" becomes "ships my machine."
One honest caveat before the tour: the image is frozen, but the
build is only as reproducible as your pins. python:3.12-slim is
a moving tag and an unpinned requirements.txt floats — so two
builds a month apart can differ. Pin dependency versions (and, for
strict builds, the base image digest) or the guarantee quietly
weakens.
The Dockerfile, annotated
FROM python:3.12-slim # exact base, not "whatever's installed"
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt # deps cached as a layer
COPY . .
EXPOSE 8000
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
Each line is a cached layer — order matters: dependencies
change rarely and copy first, code changes often and copies last,
so rebuilds after a code edit reuse the expensive pip layer. Build
and run: docker build -t churn-api . then docker run -p 8000:8000 churn-api. This image is also chapter 43's
reproducibility story applied to serving: the runtime is now
versioned, like the data and the weights. (One ML-specific note:
model weights usually don't go in the image — the container
fetches its model from the registry at startup, so deploying a new
model doesn't mean rebuilding the runtime.)
Config: the 12-factor rule
The same image must run in staging and production — so anything
that differs between environments cannot be baked into the
image. It arrives from outside, as environment variables.
Run the editor: one load_config(), two environments, zero code
changes. This is chapter 18's env-var practice promoted to
architecture — and secrets follow chapter 18 exactly: injected at
runtime by the platform's secret manager, never COPY'd into an
image (images get pushed to registries; a secret in a layer is a
secret published).
Where AI specifically gets this wrong
- Hardcoded config. Generated servers embed ports, model paths, and stage names in code — every environment now needs its own build, which defeats the entire point.
.envfiles and keys COPY'd into images. Chapter 18's leak, containerized and pushed to a registry.- No
.dockerignore. It's the gitignore of images — maintain it, or the leak above is one carelessCOPY . .away. - Layer order that busts the cache.
COPY . .before the pip install re-installs everything on every code change. Ten-minute rebuilds forever, for a two-line ordering mistake.