# Docker / containers exercise backlog

Exercises whose **provenance is the Docker documentation** and the Linux
`namespaces(7)` / `cgroups(7)` man pages rather than a book. Migrated from
`~/cld-toys/exercises` on 2026-07-25. Read the top-level `../README.md` first —
it's the format spec.

Legend: engine = **surprise** / **magnitude** / **causation**.
Status: `[ ]` candidate · `[x]` built & verified · `UNVERIFIED` couldn't run.

---

## Resume here (session state, 2026-07-26)

**Migration status: done.** All four exercises are moved, rewired, linked from
the landing page, and **re-verified on this machine** — macOS 26.5.2 (25F84) /
Apple Silicon, Docker Desktop Engine 29.6.2, Linux VM kernel
`6.12.76-linuxkit`. Every transcript in all four was re-captured from a real
run on 2026-07-26; every image is pinned by tag and digest in an Environment
block. No `UNVERIFIED` entries remain.

Full migration plan and remaining phases: `../tasks/merge-cld-toys.md`.

## Built and verified

- [x] **[namespaces](namespaces.md)** ([html](namespaces.html)) —
      **surprise**. There is no container object in the kernel: one ordinary
      process tree plus per-process filters over which kernel-table entries a
      process may see. The same `sh` is PID 1 to itself and PID 39023 to the
      VM, and `/proc/39023/status` holds both on one line (`NSpid: 39023 1`)
      while the container's own `/proc/1/status` holds only `NSpid: 1` — so
      `kill 39037` from outside removes a process the container cannot name.
      `unshare --pid --fork --mount-proc` reproduces the whole trick with no
      daemon. Re-verified 2026-07-26 on Engine 29.6.2 / kernel
      `6.12.76-linuxkit`, `alpine:3.20` (was bare `alpine` in 9 places, which
      resolves to a different digest than the declared image): all seven parts
      reproduced, every transcript re-captured against an otherwise-idle
      daemon. Several transcripts had been silently trimmed — `/proc/1/ns/`
      really has ten entries not eight, `ip -o addr` really prints
      `valid_lft`/`preferred_lft` tails, and the `lo` lines were cut. `NSpid`
      is documented in `proc_pid_status(5)`, not `namespaces(7)` as the
      Sources line claimed.
- [x] **[cgroup-memory-limits](cgroup-memory-limits.md)**
      ([html](cgroup-memory-limits.html)) — **causation**. `--memory=50m` writes
      a number to `/sys/fs/cgroup/memory.max`; exceed it and the kernel sends
      `SIGKILL` (exit 137, `memory.events` `oom_kill 1`) rather than failing an
      allocation. Your process gets no turn — but `max 37` says it survived
      three dozen collisions with the wall first. Re-verified 2026-07-26 on
      Engine 29.6.2 / kernel `6.12.76-linuxkit`, `alpine:3.20` +
      `python:3-alpine`: all six parts reproduced, every transcript re-captured.
      One arithmetic error fixed (42.4 MiB of a 50 MiB limit is 85%, not the
      89% the file claimed in four places).
- [x] **[union-filesystem-layers](union-filesystem-layers.md)**
      ([html](union-filesystem-layers.html)) — **magnitude**. An image is a stack
      of content-addressed tarballs union-mounted topmost-wins; `RUN rm` in its
      own step ships the bytes anyway (61,251,529 B vs 8,822,708 B for the same
      filesystem), a second image off the same base costs `0B` unique, and two
      containers from one image cost 17 bytes between them. Re-verified
      2026-07-26 on Engine 29.6.2 / `6.12.76-linuxkit`, `alpine:3.20`: every
      transcript re-captured, all five parts reproduced. One explanation
      rewritten — the shared `echo` layer in Part 3 is BuildKit *cache reuse*,
      not two builds independently emitting identical bytes; `--no-cache` makes
      the same command produce a different digest for a byte-identical file
      (tar mtimes), which is now its own step.
- [x] **[container-networking](container-networking.md)**
      ([html](container-networking.html)) — **surprise**. The embedded resolver
      at `127.0.0.11` is per-container but its zone is per-network, so a healthy
      resolver returns `NXDOMAIN` one bridge away — and DNS and reachability
      fail independently. Re-verified 2026-07-26 on Engine 29.6.2 /
      `nginx:alpine` (nginx 1.31.3): every transcript re-captured, all five
      parts reproduced.

## Candidates (not yet built)

None mined yet.
