feat(root04): author Containers & Docker

Adds all 8 ROOT 04 modules (why containers exist, images & containers, Docker CLI, container
networking, container storage, Dockerfile & image build, Docker Compose, containers →
Kubernetes) — 43 activities, using the nested Docker Engine Phase 6D built and tested. Modules
1, 2, and 8 are conceptual (no lab by design) — the honest bridge module (M8) explicitly names
what Kubernetes adds beyond what this course already taught, without re-teaching Kubernetes
itself.

Adds 5 new hands-on Docker scenarios plus 2 MCQs, all against the real nested Docker Engine:
docker-cli-basics (run/exec/inspect a real container), docker-container-networking (a genuine
"works internally, unreachable from host" port-publishing failure, diagnosed and fixed by
recreating the container with the correct `-p` flag), docker-container-storage (a real
destroy/recreate cycle proving named-volume persistence, not just described), docker-build-image
(a real Dockerfile, a real build, a real custom image run), and docker-compose-multiservice (a
real two-service Compose application with a real published port). All tested end-to-end via the
real scenario-validation API, including negative tests.

Wires rootnode-containers-assessment (2 required + 4 random-draw scenarios) using the existing
Phase 3B assessment engine.

This completes the entire ROOT_ Foundations sequence (ROOT 01-04) — see
docs/PHASE6E_ROOT_FOUNDATIONS_COMPLETION.md.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
2026-08-26 17:08:14 -05:00
co-authored by Claude Sonnet 5
parent e68b6f445a
commit 682ef2de47
45 changed files with 1970 additions and 0 deletions
@@ -0,0 +1,61 @@
# Container Storage
## The default: a disposable writable layer
Every running container gets its own writable layer sitting on top of its image's read-only
layers. Anything written there — a log file, a temp file, application data — exists ONLY as long
as that specific container does. Destroy the container, and that data is gone permanently. This
is ROOT 02's "persistent vs. temporary" question, given a hard default answer: by default,
everything in a container is temporary.
## Bind mounts: sharing a specific host path
A **bind mount** connects a specific path on the HOST filesystem directly into the container:
```
docker run -v /host/path:/container/path myimage
```
Changes are visible on BOTH sides immediately — useful for development (edit code on the host,
see changes in the container instantly) but tightly coupled to that exact host's filesystem
layout, which makes it a poor choice for portable, production data.
## Named volumes: Docker-managed persistent storage
A **named volume** is storage Docker manages for you, independent of any specific host path:
```
docker volume create my-data
docker run -v my-data:/container/path myimage
```
A named volume's data survives container destruction AND recreation — attaching the SAME volume
to a brand-new container gives that new container access to exactly the same data the old one
left behind. This is the deliberate, production-appropriate way to persist container data.
## The test that actually proves persistence
Claiming "this data persists" isn't convincing without proof: write data, DESTROY the container
completely, create a brand new one attached to the same volume, and confirm the data is still
there. This module's Lab requires exactly that proof, not just a claim.
## Bridge to Kubernetes
A Kubernetes `PersistentVolume`/`PersistentVolumeClaim` is the same idea generalized across an
entire cluster instead of one machine — the exact concept this module teaches, just orchestrated
at a larger scale, which Kubernetes courses build on directly.
## Instructor Notes
**Lesson goal:** Make "containers are disposable, volumes are not" a hard, memorable
distinction — directly building on ROOT 02's persistent/temporary framing.
**Approximate timing:** 15 minutes read; 10 minutes live.
**Common misconception to watch for:** assuming ANY container data survives by default — the
default is the OPPOSITE; persistence requires deliberate action (a volume or bind mount).
**Discussion question:** "Why might a named volume be preferred over a bind mount for
production data, even though both technically 'persist' data?" (Answer: a named volume isn't
tied to one specific host's exact filesystem path, making it more portable across different
hosts/environments.)
@@ -0,0 +1,63 @@
# Demonstration: Proving Data Loss, Then Proving Persistence
## OBSERVE
```
$ docker run -d --name no-volume alpine:3.19 sleep 300
$ docker exec no-volume sh -c "echo important-data > /tmp/data.txt"
$ docker exec no-volume cat /tmp/data.txt
important-data
```
Data exists — but is it actually safe?
## HYPOTHESIS
Predict: will `/tmp/data.txt` still exist if this exact container is destroyed and an identical
new one is started?
## INSPECT
```
$ docker rm -f no-volume
$ docker run -d --name no-volume alpine:3.19 sleep 300
$ docker exec no-volume cat /tmp/data.txt
cat: /tmp/data.txt: No such file or directory
```
Gone — proven, not assumed. The "new" container is a completely fresh writable layer, even
though it has the exact same name and image.
## CHANGE
Now with a named volume instead:
```
$ docker rm -f no-volume
$ docker volume create demo-vol
$ docker run -d --name with-volume -v demo-vol:/data alpine:3.19 sleep 300
$ docker exec with-volume sh -c "echo important-data > /data/data.txt"
$ docker rm -f with-volume
$ docker run -d --name with-volume -v demo-vol:/data alpine:3.19 sleep 300
```
## VERIFY
```
$ docker exec with-volume cat /data/data.txt
important-data
```
Proven, not assumed — the exact same destroy-and-recreate sequence, but this time the data
survived, because it lived in the volume, not the container's disposable writable layer.
## Instructor Notes
**Demo goal:** Prove BOTH the loss and the persistence with real destroy/recreate cycles — the
contrast is what makes this lesson land, not either half alone.
**Approximate timing:** 10 minutes live.
**Common mistake to watch for:** stopping (not removing) a container and concluding "data
persists" — `docker stop` doesn't touch the writable layer at all; only `docker rm` (destroying
the container entirely) actually tests real persistence.
@@ -0,0 +1,39 @@
# Knowledge Check: Container Storage
---
**1. By default, what happens to data written inside a running container when that container is
destroyed?**
*Answer:* It's lost permanently — a container's writable layer is disposable by default; nothing
persists unless a volume or bind mount was explicitly configured.
---
**2. What's the actual difference between a bind mount and a named volume?**
*Answer:* A bind mount connects a specific HOST filesystem path directly into the container
(tightly coupled to that host's exact layout). A named volume is storage Docker manages
independently of any specific host path, making it more portable and the production-appropriate
choice.
---
**3. Does `docker stop` (without removing) test whether a container's data actually persists?**
*Answer:* No — stopping doesn't touch the writable layer at all. Only fully removing
(`docker rm`) and recreating the container is a real test of persistence.
---
**4. What Kubernetes concept is this module's volume idea the direct foundation for?**
*Answer:* PersistentVolume/PersistentVolumeClaim — the same persistence concept, generalized
across an entire cluster instead of one machine.
## Instructor Notes
**Facilitation:** Question 3 is worth emphasizing — it's a genuine, common mistake in verifying
persistence claims.
**Approximate timing:** 5 minutes.
@@ -0,0 +1,29 @@
# Guided Exercise: Predict What Survives Before You Test It
This is a paper exercise — commit to predictions before working in the Lab.
## Your task
For each scenario, predict whether the data survives a `docker rm -f` followed by recreating a
container with the same name and image:
1. Data written to `/tmp/file.txt` inside a container with no volumes configured.
2. Data written to `/data/file.txt`, where `/data` is mounted from a named volume.
3. Data written to `/data/file.txt` in scenario 2, but this time you ALSO run
`docker volume rm <the-volume>` before recreating the container.
## A worked shape to check yourself against
1. Lost — no volume, so it lived only in the disposable writable layer.
2. Survives — the volume itself is untouched by the container's destruction, and the new
container reattaches to the same volume.
3. Lost — the volume itself was explicitly deleted, not just the container. Persistence depends
on the VOLUME surviving, not just avoiding container destruction.
## Instructor Notes
**Facilitation:** Item 3 is the important trick question — students who've internalized
"volumes always persist" without qualification should be corrected here: the volume itself can
still be deleted deliberately.
**Approximate timing:** 8-10 minutes live discussion; 5 minutes self-paced.
@@ -0,0 +1,27 @@
# Reflection: You Proved Persistence, You Didn't Just Trust It
## Reflection questions
1. Before this module, would you have assumed container data persists by default? How
expensive would that wrong assumption be in a real production incident?
2. Why did this module insist on actually DESTROYING a container to test persistence, rather
than just describing the concept?
## Real Ops takeaway
**"I assumed it would persist" is one of the most expensive assumptions in real operations
work.** You didn't just learn that volumes persist data — you personally destroyed a container
and confirmed data loss, then destroyed another and confirmed data survival, with the SAME
sequence of commands each time except for one deliberate volume configuration. That's the level
of confidence "verify, don't assume" is supposed to give you, and it's exactly the discipline
ROOT 02's troubleshooting framework already built.
This module is complete. Continue to the next module.
## Instructor Notes
**Discussion questions for a live classroom:** question 1 works well as a real cautionary
example — ask if anyone has heard of (or experienced) a real data-loss incident caused by this
exact wrong assumption.
**Approximate timing:** 5-8 minutes live discussion; 5 minutes self-paced writing.