fivenines
12/40

Guided Problem

MiniDock Build 08: Exclude Build Tools from the Release

Time
20m
Level
foundation
Artifacts
not specified
Progress0%
Lesson 12 · Images

Multi-Stage Builds

Design claim: Build and runtime environments should be separate products connected by explicit artifact transfer.

Starting model

  • You can reuse unaffected build work without permitting stale output after an input change.
  • The useful vocabulary at this point is deliberately small: build stage, runtime stage, artifact copy, toolchain.

Construction needs are not runtime needs

A compiled service needs toolchains and source code during construction but should ship only the runtime files required in production. 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: use one stage and remove build tools near the end. The shortcut looks efficient until immutable history, shared content, or reproducibility becomes observable. Deletion in a later layer does not remove bytes from earlier immutable layers, and broad toolchains increase image size and attack surface. The constraint turns a convenient file operation into an identity or ordering bug.

Cross the boundary with artifacts, not environments

Compiling inside a build means your image contains a compiler — forever. A 10 MB binary rides atop 900 MB of toolchain layers that serve no runtime purpose and widen the attack surface. Deleting the tools in a later instruction doesn't help: the earlier layers still exist underneath (Lesson 9 — lowers are immutable). The layered model itself makes subtraction impossible; the fix has to be structural.

Multiple build stages create independent filesystem histories, and selected artifacts cross into a minimal runtime stage through explicit copy operations. 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.

Architecture — two pipelines, one artifact bridge
  flowchart LR
    subgraph S1["stage 1: builder (discarded)"]
      F1["FROM toolchain (900 MB)"] --> C1["COPY src/"] --> R1["RUN compile → /out/bin"]
    end
    subgraph S2["stage 2: runtime (shipped)"]
      F2["FROM minimal base (10 MB)"] --> C2["COPY --from=builder /out/bin"] --> M2["CMD /bin"]
    end
    R1 -- "artifact bridge:
only /out/bin crosses" --> C2 S2 --> IMG["final image ≈ 20 MB
(zero toolchain layers)"] style S1 fill:#fff8ec,stroke:#f2ddb0 style IMG fill:#f0faf2,stroke:#1f7a34

It separates builder and runtime stage histories with one explicit artifact edge. Its central claim is that the runtime image contains only artifacts explicitly transferred from build stages onto its own base; the labels therefore describe authority rather than decorative grouping.

Start the runtime history clean

A multi-stage file defines multiple FROM stages, each its own pipeline with its own layers. The magic instruction is COPY --from=builder: it reaches into a previous stage's filesystem and copies files into the current stage. The final image contains only the last stage's layers; earlier stages are scaffolding, discarded after the build. Stages without dependencies can even build in parallel.

The builder compiles in a tool-rich stage, starts a fresh runtime base, and copies only the application binary and declared runtime assets. 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 orders compilation before a selective copy into a fresh runtime base.

Sequence — what the builder actually does
  sequenceDiagram
    participant B as Builder
    participant S1 as stage 1 (toolchain)
    participant S2 as stage 2 (minimal)
    B->>S1: build all stage-1 layers (compile)
    S1-->>B: filesystem contains /out/bin
    B->>S2: build stage-2 layers
    B->>S1: read /out/bin
    B->>S2: COPY into stage 2 → one small layer
    B-->>B: tag ONLY stage 2 as the image
    note over S1: stage 1 layers stay in build cache
but never ship

It orders compilation before a selective copy into a fresh runtime base. The ordering is valid only when it continues to preserve the stated invariant under retries and interruption.

Unreachable build layers stay out of the release

A compiler secret or source file exists in the builder stage, so the final manifest must not make any layer containing it reachable. A partial or stale result must remain distinguishable from a complete image state; otherwise the cache or store begins to lie.

The runtime image contains only artifacts explicitly transferred from build stages onto its own base. The invariant is phrased in bytes, ownership, or completed prefixes so it remains true across storage implementations.

Split MiniDock's build and ship products

MiniDock separates construction capability from production authority. MiniDock can adopt the content rule without preselecting the learner's attachment point.

What carries forward

  • The runtime image contains only artifacts explicitly transferred from build stages onto its own base.
  • Keep build-only tools and partial artifacts out of the runtime image.
  • The rejected shortcut remains a diagnostic: if the design starts depending on it again, the original constraint has probably been lost.
Next step

See what actually stuck.

Take the practice scenarios now.