Theory Tutorial
Building Images
- Time
- 10m
- Level
- not specified
- Artifacts
- theory + practice
Building Images
Design claim: An image build is an ordered transformation from declared inputs to immutable snapshots, with an explicit boundary around each step.
Starting model
- You can give containers independent writable views without copying or mutating shared image layers.
- The useful vocabulary at this point is deliberately small: build instruction, temporary container, snapshot, layer diff.
A build should explain its history
A Dockerfile must produce a reviewable image history rather than mutate an unknown host into an undocumented state. Treat the release as data with identity and history, not as the accidental contents of one machine. Reuse is safe only after that data model is precise.
The tempting shortcut is straightforward: run build commands directly on the host and archive the final directory. The shortcut looks efficient until immutable history, shared content, or reproducibility becomes observable. Host execution leaks local tools and state into the result, obscures instruction boundaries, and makes intermediate failure difficult to reproduce. The constraint turns a convenient file operation into an identity or ordering bug.
Turn commands into immutable transitions
The builder's core loop is elegantly circular: to build an image, run containers. For each instruction — "copy these files in", "run this install command", "set this env var" — the builder starts a temporary container from the layers built so far, applies the instruction, and captures the result. Filesystem-changing instructions (COPY, RUN) produce a new layer (the upper dir, tarred). Metadata instructions (ENV, CMD, EXPOSE) touch only the config — no layer at all. The inputs are the build file plus a build context: the directory of files the client ships to the daemon, the only files COPY can ever see.
The builder applies instructions in order against a controlled root filesystem and commits filesystem changes as immutable layers with updated configuration. The mechanism makes inputs and resulting content explicit, allowing later stages to reuse facts instead of trusting names or mutable directories.
Read the structure from references toward immutable content, noting where ordering or private state changes the meaning of an otherwise shared object.
flowchart LR
BF["build file
(instruction list)"] --> B["builder"]
CTX["build context
(files sent by client)"] --> B
B --> TMP["temp container
from layers so far"]
TMP -- "capture upper dir" --> NL["new layer"]
NL -- "becomes base for
next instruction" --> TMP
B --> CFG["image config
(cmd, env — no layers)"]
NL --> IMG["final image:
layers + config + manifest"]
CFG --> IMG
It maps ordered build instructions to successive image states. Its central claim is that every published build state corresponds to a completed prefix of declared instructions; the labels therefore describe authority rather than decorative grouping.
Each completed prefix becomes a valid state
The builder's core loop is elegantly circular: to build an image, run containers. For each instruction — "copy these files in", "run this install command", "set this env var" — the builder starts a temporary container from the layers built so far, applies the instruction, and captures the result. Filesystem-changing instructions (COPY, RUN) produce a new layer (the upper dir, tarred). Metadata instructions (ENV, CMD, EXPOSE) touch only the config — no layer at all. The inputs are the build file plus a build context: the directory of files the client ships to the daemon, the only files COPY can ever see.
Each instruction starts from the preceding snapshot, performs its declared operation, records the diff or metadata change, and hands a new state to the next instruction. Follow identity through the operation and ask which completed fact authorizes each reuse or transition.
The second figure tests the same model in motion: it shows temporary execution, snapshot commitment, and failure before publication.
sequenceDiagram
participant C as Client
participant B as Builder (in daemon)
participant T as Temp container
C->>B: build (sends build file + context)
B->>T: start from base image, then COPY app/ /app
T-->>B: diff captured → layer 1
B->>T: start from base+L1, then RUN install deps
T-->>B: diff captured → layer 2
B->>B: CMD "serve" → config only, no container needed
B-->>C: image built: base + L1 + L2 + config
note over B: each snapshot is a normal layer —
the image is its own build history
It shows temporary execution, snapshot commitment, and failure before publication. The ordering is valid only when it continues to preserve the stated invariant under retries and interruption.
Failure must not publish a half-step
An instruction fails after modifying its temporary environment, so no partially completed snapshot may become the image's next valid state. A partial or stale result must remain distinguishable from a complete image state; otherwise the cache or store begins to lie.
Every published build state corresponds to a completed prefix of declared instructions. The invariant is phrased in bytes, ownership, or completed prefixes so it remains true across storage implementations.
Give MiniDock a controlled builder
MiniDock's builder can expose instruction boundaries without leaking its temporary execution machinery into the image contract. MiniDock can adopt the content rule without preselecting the learner's attachment point.
What carries forward
- Every published build state corresponds to a completed prefix of declared instructions.
- Trace how an ordered build recipe becomes immutable layers plus runtime configuration.
- The rejected shortcut remains a diagnostic: if the design starts depending on it again, the original constraint has probably been lost.
See what actually stuck.
Take the practice scenarios now.