Guided Problem
MiniDock Build 09: Recover Local State by Its Owner
- Time
- 25m
- Level
- intermediate
- Artifacts
- not specified
The On-Disk Stores
Design claim: Local engine state should be split by object identity and lifecycle instead of hidden behind one undifferentiated directory.
Starting model
- You can keep build-only tools and partial artifacts out of the runtime image.
- The useful vocabulary at this point is deliberately small: reference store, image store, layer store, container store.
Different objects fail differently
The engine must recover references, image metadata, blobs, snapshots, container records, and volumes without confusing their owners or cleanup rules. 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: store every object in one generic key-value namespace. The shortcut looks efficient until immutable history, shared content, or reproducibility becomes observable. References are mutable, blobs are immutable, snapshots are mounted, container records track lifecycle, and volumes outlive containers. The constraint turns a convenient file operation into an identity or ordering bug.
Align storage boundaries with ownership
Persistence gets simple when each store answers one question. Reference store: "what digest does this name point to?" — a tiny mutable map, the only place where names live. Image store: "what is this image's config and layer list?" — immutable, digest-keyed. Layer store: "where is this layer extracted on disk, and who is its parent?" — it stores chains, because layer 3 extracted on top of layers 1–2 is a different filesystem than layer 3 alone. Container store: "what containers exist, from which image, in what state?" — the only store that changes at runtime.
Separate stores expose contracts aligned with each object's identity, owner, consistency boundary, and garbage-collection rule. 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
E["engine"]
RS[("reference store
name → digest")]
IS[("image store
digest → config + layer list")]
LS[("layer store
diff → extracted dir + parent chain")]
CS[("container store
id → image, state, mounts")]
E -- "resolve names" --> RS
E -- "read configs" --> IS
E -- "mount chains" --> LS
E -- "track instances" --> CS
RS -. "points into" .-> IS
IS -. "references" .-> LS
CS -. "instantiated from" .-> IS
It separates reference, image, content, snapshot, container, and volume stores. Its central claim is that each persisted object has one authoritative store and one lifecycle owner; the labels therefore describe authority rather than decorative grouping.
Resolve from names to runnable state
Persistence gets simple when each store answers one question. Reference store: "what digest does this name point to?" — a tiny mutable map, the only place where names live. Image store: "what is this image's config and layer list?" — immutable, digest-keyed. Layer store: "where is this layer extracted on disk, and who is its parent?" — it stores chains, because layer 3 extracted on top of layers 1–2 is a different filesystem than layer 3 alone. Container store: "what containers exist, from which image, in what state?" — the only store that changes at runtime.
A reference resolves to image metadata, metadata identifies blobs, snapshot state prepares a mount, and the container store records the living instance. 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 traces resolution across stores and exposes partial-state recovery boundaries.
sequenceDiagram
participant C as CLI
participant E as Engine
participant RS as Reference store
participant IS as Image store
participant LS as Layer store
C->>E: list images
E->>RS: all name → digest entries
RS-->>E: api:2.1 → sha256:9f3c…, web:1.0 → sha256:77aa…
E->>IS: config for each digest
IS-->>E: created-at, layer lists
E->>LS: total size of each layer chain
LS-->>E: sizes (shared layers counted once)
E-->>C: table: name, id, created, size
It traces resolution across stores and exposes partial-state recovery boundaries. The ordering is valid only when it continues to preserve the stated invariant under retries and interruption.
Recovery needs to know who committed
The daemon restarts after one store commits and another does not, so recovery must identify the incomplete owner-specific operation. A partial or stale result must remain distinguishable from a complete image state; otherwise the cache or store begins to lie.
Each persisted object has one authoritative store and one lifecycle owner. The invariant is phrased in bytes, ownership, or completed prefixes so it remains true across storage implementations.
Give MiniDock stores with contracts
MiniDock can recover and clean local state by following ownership rather than scanning an opaque pile of files. MiniDock can adopt the content rule without preselecting the learner's attachment point.
What carries forward
- Each persisted object has one authoritative store and one lifecycle owner.
- Recover local engine state without confusing mutable names, immutable image content, and instance state.
- 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.