fivenines
26/40

Guided Problem

MiniDock Build 21: Keep a Volume Beyond Its Containers

Time
25m
Level
intermediate
Artifacts
not specified
Progress0%
Lesson 26 · Storage

Volume Lifecycle and Drivers

Design claim: A volume is an engine-owned resource with a lifecycle independent of any container that happens to mount it.

Starting model

  • You can choose storage ownership and lifetime independently from a container's writable layer.
  • The useful vocabulary at this point is deliberately small: volume lifecycle, attach, detach, reference count.

Consumers are shorter-lived than data

Database data must survive container removal, move between replacement containers, and use different storage backends without changing container commands. 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: create and delete the volume as a child of the first container. The shortcut treats every writable path as if mounting and ownership were the same relationship. Containers are replaceable consumers, while the data may have a longer lifetime and may be mounted by later or multiple consumers. Replacement or cleanup then reveals that consumer lifetime and data lifetime were never equivalent.

Give the volume its own state machine

The whole point of a volume is to outlive containers, so its lifecycle can't be nested inside theirs. The engine tracks each volume with a reference count of attached containers: remove a container and the volume detaches but persists (refcount decrements); a volume is only removable when its refcount is zero, and even then only by an explicit command or prune — never as a side effect. Ten containers can attach one volume simultaneously (shared state — useful, and a consistency footgun the app must handle). The corner case every engine faces: run -v data:/db where data doesn't exist yet — auto-create it, because requiring a separate create step first breaks the common path.

The engine keeps a volume object and reference lifecycle, while a driver implements create, mount, unmount, and remove against a backend. 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.

Architecture — volume states and the refcount
  stateDiagram-v2
    [*] --> Exists: create (or auto-create on first use)
    Exists --> Attached: container starts with -v (refcount 1+)
    Attached --> Attached: more containers attach (refcount++)
    Attached --> Exists: last container removed (refcount 0)
    Exists --> [*]: explicit volume rm / prune
    note right of Exists
      refcount 0 = "dangling" —
      still never auto-deleted
    end note
      

It models volume creation, mounting, unmounting, and explicit removal. Its central claim is that container removal changes mount references but never implies volume removal unless policy explicitly requests it; the labels therefore describe authority rather than decorative grouping.

Attach and lifetime are separate transitions

"Give me a directory to mount" is an interface, not an implementation. The default local driver makes a host directory; an NFS driver mounts a remote share; a cloud driver attaches a network disk. The engine speaks four verbs — create, mount, unmount, remove — and never knows which backend answers:

Create establishes the named resource, mount obtains an attachment path, unmount releases the attachment, and explicit removal ends the volume lifetime. 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 replaces one container while preserving and reattaching the volume.

Sequence — run -v data:/db with a driver, volume doesn't exist yet
  sequenceDiagram
    participant E as Engine
    participant VS as Volume store
    participant DR as Driver (local / nfs / cloud)
    participant C as Container setup
    E->>VS: does volume "data" exist?
    VS-->>E: no
    E->>DR: create("data")
    DR-->>E: ready
    E->>DR: mount("data")
    DR-->>E: host path /mnt/…/data
    E->>C: bind that path over /db in the rootfs
    E->>VS: refcount("data") = 1
    note over VS: container rm later → refcount 0,
volume and bytes remain

It replaces one container while preserving and reattaching the volume. The ordering is valid only when it continues to preserve the stated invariant under retries and interruption.

Container removal is not data deletion

A container is removed while its named volume is needed by a replacement, so container cleanup must release attachment without deleting the volume. A precise storage boundary reports incomplete cleanup without erasing data or claiming a backend action succeeded.

Container removal changes mount references but never implies volume removal unless policy explicitly requests it. The invariant states lifetime and authority independently of the currently selected backend.

Let MiniDock own volumes independently

MiniDock separates durable resource ownership from ephemeral container membership. MiniDock can choose a storage mechanism only after preserving that ownership rule.

What carries forward

  • Container removal changes mount references but never implies volume removal unless policy explicitly requests it.
  • Keep durable data across container replacement and refuse unsafe volume removal.
  • 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.