Foundations: Python, Git and toolsLoading progress…

Containers and reproducibility

Build a small HTTP service with clear boundaries, tests, logs and a repeatable runtime.

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 starts

Pinning 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).

Build, then edit app.py and build again, in both layouts. The times are illustrative round numbers, not measurements.

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.

Write some rows, replace the container, then repeat with a volume mounted.

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.

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.