Guided Problem
MiniDock Build 22: Hide Layer Backends Behind One Contract
- Time
- 25m
- Level
- intermediate
- Artifacts
- not specified
Storage Drivers
Design claim: A storage-driver seam preserves one snapshot contract while allowing file-level and block-level backends to differ.
Starting model
- You can keep durable data across container replacement and refuse unsafe volume removal.
- The useful vocabulary at this point is deliberately small: storage driver, file-level CoW, block snapshot, layer contract.
Backends differ below a stable promise
The engine must prepare writable container filesystems on hosts whose storage backends have different snapshot, mount, and cleanup mechanics. Storage design starts with who owns the bytes and how long they must live. Backend mechanics come only after those promises are explicit.
The tempting shortcut is straightforward: let the daemon call backend-specific filesystem and block commands directly. The shortcut treats every writable path as if mounting and ownership were the same relationship. Backend details would leak through lifecycle code, making portability, testing, and safe cleanup depend on the selected host technology. Replacement or cleanup then reveals that consumer lifetime and data lifetime were never equivalent.
Expose semantics, hide mechanics
A driver contract exposes prepare, mount, snapshot, and remove operations while each implementation maps them to its backend's primitives. The mechanism gives the durable resource or driver its own contract so containers can attach without silently inheriting ownership.
Read the ownership edges separately from mount edges; they answer different cleanup and recovery questions.
flowchart TB
E["engine"] --> IF["storage driver interface
create · mount · diff · apply · delete"]
IF --> OV["overlay-style (default)
file-level CoW
works on any host"]
IF --> SN["snapshot-style (btrfs / zfs)
block-level CoW
needs that filesystem"]
OV --> D1["copy-up cost:
whole file"]
SN --> D2["copy-up cost:
changed blocks only"]
IMG["images (layer tars)"] -. "identical regardless
of driver" .-> IF
It places the engine contract above file and block implementations. Its central claim is that the engine depends on storage semantics, while the driver alone owns backend-specific operations; the labels therefore describe authority rather than decorative grouping.
One lifecycle calls many possible backends
Every storage driver answers the same five questions: create a layer from a parent, mount a stack, capture a diff, apply a diff, delete a layer. What differs is the physics underneath. Overlay-style drivers work at file granularity: simple, fast to set up, but first-write copy-up is proportional to file size. Snapshot-style drivers (btrfs, zfs, devicemapper) use the filesystem's native copy-on-write at block granularity: modifying one block of a 5 GB file copies kilobytes, not gigabytes — but they demand that specific filesystem under the engine's storage directory. Images don't care: layers are just tars (Lesson 7); the driver is a per-host deployment decision, not part of the image contract.
The engine requests a snapshot by parent identity, receives a mountable view, uses it through the container lifecycle, then releases and removes it through the same driver. Track create, attach, use, detach, and remove as distinct transitions rather than one container operation.
The second figure tests the same model in motion: it traces prepare, mount, release, and cleanup through one selected driver.
sequenceDiagram
participant B as Builder
participant IF as Driver interface
participant OV as overlay driver
participant SN as snapshot driver
B->>IF: diff(layer) — what changed?
alt overlay backend
IF->>OV: diff
OV-->>IF: walk upperdir, tar the files
else snapshot backend
IF->>SN: diff
SN-->>IF: filesystem reports changed blocks → tar
end
IF-->>B: identical tar format either way
It traces prepare, mount, release, and cleanup through one selected driver. The ordering is valid only when it continues to preserve the stated invariant under retries and interruption.
Driver failure must remain observable
A backend cannot create a snapshot or cleanup fails after unmount, so the driver must report the precise incomplete state without pretending success. A precise storage boundary reports incomplete cleanup without erasing data or claiming a backend action succeeded.
The engine depends on storage semantics, while the driver alone owns backend-specific operations. The invariant states lifetime and authority independently of the currently selected backend.
Give MiniDock a storage seam
MiniDock gains a narrow storage waist that can hide overlay-style filesystems or block snapshots behind one contract. MiniDock can choose a storage mechanism only after preserving that ownership rule.
What carries forward
- The engine depends on storage semantics, while the driver alone owns backend-specific operations.
- Use different host storage backends without changing image identity or engine consumers.
- 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.