Containers and reproducibility
Build a small HTTP service with clear boundaries, tests, logs and a repeatable runtime.
THE IDEA
Works on my machine
Your program depends on more than your code: the language version, installed libraries, system packages, environment variables. If any differ between your laptop and the server, the same code can behave differently. A container image packages the program together with its environment, so each start begins from the same filesystem contents. A Dockerfile is the recipe, one instruction at a time.
FROM python:3.12-slim # start from a base image (pin a version tag)
WORKDIR /app # directory for what follows
COPY requirements.txt . # put one file into the image
RUN pip install -r requirements.txt
COPY . . # put the rest of the code in
CMD ["python", "app.py"] # what runs when a container startsPinning matters: python:latest means whatever is newest when you build, so two builds a month apart can differ. A specific tag, and exact versions in requirements.txt (name==version), make rebuilds repeatable.
Layers and the build cache
Each instruction produces a layer. When you rebuild, Docker reuses a layer if the instruction and everything before it is unchanged (for COPY, also the content of the files copied). The moment one layer is rebuilt, every later layer is rebuilt too. So the order of lines decides how much work an edit causes: put things that change rarely first and things that change often last.
FROM python:3.12-slim WORKDIR /app COPY . . RUN pip install -r requirements.txt CMD ["python", "app.py"]
Builds so far: 0. Illustrative total: 0 s (pip install 40, each COPY 1, metadata lines 0).
Predict first. With COPY . . before RUN pip install, you change one line of app.py and rebuild. What happens to the install?
What a container does not give you
A container does not make configuration, secrets, permissions or data disappear. A container starts with a fresh writable layer from the image; when you remove it and run a new one, anything written only in the old one is gone. Data that must survive belongs in a volume or an external service. Startup (the command) and health checks are described separately from the business logic, so the platform can tell a running process from a healthy one.
container #1
rows in its own writable layer: 0
volume
not mounted
Replacing a container creates a fresh one from the image. Whatever was written only in the old container's own layer is discarded with it. A volume lives outside the container, so a new container can mount it again.
Predict first. Does rebuilding an image automatically preserve a database's data?
Chapter context
Engineering makes a program understandable when it fails or changes. Separate pure calculations from external I/O so behavior can be checked cheaply and errors can be connected to specific inputs.
TRY IT YOURSELF
- The starting Dockerfile is broken and slow. Fix it using the controls: reorder, delete and add lines, and choose a pinned base image.
- Five builds are simulated in a row: the first build, then edits to
app.py, then torequirements.txt, then toapp.pyagain. All five checks must tick. - This is a simulation of the layer cache rules written for this course. It does not run Docker.
Dockerfile (edit it)
Five builds in a row
Build failed: requirements.txt: no such file (it has not been copied into the image yet)
- todo The Dockerfile builds a runnable image in all five builds
Hint: A step cannot use a file before COPY has put it in the image. Check the order, and that CMD is last. - todo The base image is pinned to a version tag, not latest
Hint: FROM python:latest changes underneath you. Use a tag such as python:3.12-slim. - todo Editing only app.py does not rerun pip install (builds 2, 3 and 5)
Hint: Copy requirements.txt and install first. Copy the rest of the code after that, so code edits only invalidate layers below the install. - todo Editing requirements.txt does rerun pip install (build 4)
Hint: pip install must come after a COPY that includes requirements.txt, so a change there invalidates it. - todo All five builds together take at most 90 seconds (illustrative times)
Hint: Only two pip installs are needed: the first build and the requirements change. Everything else should be near zero.
Stuck, or something not working? You can come back to it any time.
CHECK YOUR UNDERSTANDING
Does rebuilding an image automatically preserve a database's data?
Chapter reference: math, worked example & code
This section belongs to the whole chapter, so it is the same on every lesson in it and may cover a different lesson's topic. The code for the experiment you just ran is inside the experiment itself.
Math and notation
For money examples, use integer minor units rather than binary floating point. The subtotal is sum(quantity × unit_price), and the domain must specify rounding and tax rules before those operations are implemented.
Worked example
Two items cost 125 minor units each and three cost 40 each. The subtotal is 2×125 + 3×40 = 370. A test can exercise this calculation without starting an HTTP server. Request parsing and response formatting need separate checks.
Reference code
The following example uses the Python standard library and does not call a remote service.
def subtotal(items):
return sum(quantity * price for quantity, price in items)
assert subtotal([(2, 125), (3, 40)]) == 370
assert subtotal([]) == 0
Python examples are provided for study and local execution; this page does not execute Python.
Common mistakes
Test coverage counts executed lines, not verified requirements. A container does not turn an unsafe operation into a safe one. JSON validity alone does not establish application validity.