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,50 @@
# Dockerfile and Image Build
## A Dockerfile is a recipe for an image
Every image you've used so far (`nginx:alpine`, `alpine:3.19`) was itself BUILT from a
Dockerfile by someone else. This module teaches you to write your own.
## The core instructions
- **`FROM <image>`** — every Dockerfile starts by choosing a base image to build on top of.
Almost nothing is built completely from scratch.
- **`WORKDIR <path>`** — sets the working directory for every instruction that follows it (and
for the container's default working directory when it runs).
- **`COPY <host-path> <image-path>`** — copies files from your BUILD CONTEXT (the directory you
run `docker build` from) into the image.
- **`RUN <command>`** — executes a command DURING the build, creating a new layer with its
result (installing a package, for example).
- **`CMD <command>`** (conceptually) — sets the DEFAULT command a container runs when started
from this image, if nothing else is specified.
- **`ENTRYPOINT <command>`** (conceptually) — similar to `CMD`, but harder to override at
`docker run` time — the two are often combined (`ENTRYPOINT` as the fixed program,
`CMD` as its default arguments).
## Build context and `.dockerignore`
`docker build -t mytag .` — that final `.` is the **build context**: the directory Docker can
see and copy files FROM during the build. A `.dockerignore` file (same idea as `.gitignore` from
ROOT 03) excludes files from the build context — keeping large or irrelevant files (like a local
`.git` folder or dependency caches) out of your image and speeding up builds.
## Layers, again
Each instruction in a Dockerfile creates one layer, cached individually. Docker reuses cached
layers for instructions that haven't changed since the last build — which is why the ORDER of
instructions matters for build speed: put things that change rarely (like `FROM` and installing
dependencies) before things that change often (like copying your actual application code), so a
small code change doesn't invalidate expensive earlier layers.
## Instructor Notes
**Lesson goal:** Give students enough Dockerfile literacy to write a real, small, working image
— not exhaustive coverage of every possible instruction.
**Approximate timing:** 18 minutes read; 10 minutes live.
**Common misconception to watch for:** copying application code before installing dependencies
— reinforce the "changes-rarely-first" layer-ordering principle with a concrete example.
**Demo idea:** build the same Dockerfile twice with no changes, showing the second build using
fully cached layers (`CACHED` in the output) and finishing almost instantly.
@@ -0,0 +1,62 @@
# Demonstration: Watching Layer Caching Actually Work
## OBSERVE
```
$ docker build -t demo:1 .
Step 1/3 : FROM alpine:3.19
Step 2/3 : WORKDIR /app
Step 3/3 : COPY greeting.txt .
Successfully tagged demo:1
```
## HYPOTHESIS
Predict: if you run the EXACT same build again with no changes, will it take the same amount of
time?
## INSPECT
```
$ docker build -t demo:1 .
Step 1/3 : FROM alpine:3.19
Step 2/3 : WORKDIR /app
---> Using cache
Step 3/3 : COPY greeting.txt .
---> Using cache
Successfully tagged demo:1
```
`Using cache` — nearly instant, because nothing actually changed since the last build.
## CHANGE
Now change `greeting.txt`'s content and rebuild:
```
$ echo "updated" > greeting.txt
$ docker build -t demo:1 .
Step 2/3 : WORKDIR /app
---> Using cache
Step 3/3 : COPY greeting.txt .
---> (this step re-runs — the file changed)
```
Only the step AFTER the change re-runs — `WORKDIR` (unaffected) still used its cache.
## VERIFY
This is exactly why instruction ORDER matters: everything before the first changed instruction
stays cached; everything from that point onward must rebuild. Put your least-frequently-changing
instructions first.
## Instructor Notes
**Demo goal:** Make layer caching a directly OBSERVED behavior (via real build output), not an
abstract efficiency claim.
**Approximate timing:** 8 minutes live.
**Common mistake to watch for:** ordering a Dockerfile with frequently-changing application code
copied BEFORE installing rarely-changing dependencies — invalidates the expensive dependency-
install cache on every trivial code change.
@@ -0,0 +1,38 @@
# Knowledge Check: Dockerfile and Image Build
---
**1. What does `WORKDIR` actually set?**
*Answer:* The working directory for every subsequent instruction in the build, and the
container's default working directory when it runs.
---
**2. What is the "build context"?**
*Answer:* The directory (usually specified as `.` at the end of `docker build -t tag .`) that
Docker can see and copy files FROM during the build — `COPY` instructions read from it.
---
**3. Why does instruction order in a Dockerfile affect build speed?**
*Answer:* Docker caches each instruction's result as a layer and reuses cached layers for
unchanged instructions — everything before the first ACTUALLY-changed instruction stays cached,
so putting rarely-changing instructions (like dependency installs) before frequently-changing
ones (like application code) minimizes unnecessary rebuilding.
---
**4. What's `.dockerignore` for, and what other file does it resemble?**
*Answer:* It excludes files from the build context (keeping large/irrelevant files out of the
image and speeding up builds) — the same idea as ROOT 03's `.gitignore`.
## Instructor Notes
**Facilitation:** Question 3 directly reinforces the caching demo — a good check that the
concept, not just the observation, landed.
**Approximate timing:** 5 minutes.
@@ -0,0 +1,35 @@
# Guided Exercise: Order These Instructions for Maximum Cache Efficiency
This is a paper exercise — commit to an order before checking it in the Lab.
## Your task
You need a Dockerfile with these five things, in SOME order. Application code changes very
often; dependencies change rarely. Write the most cache-efficient order:
- `FROM python:3.11-slim`
- `COPY app.py .` (your actual application code — changes often)
- `COPY requirements.txt .` (dependency list — changes rarely)
- `RUN pip install -r requirements.txt` (installs dependencies — slow)
- `WORKDIR /app`
## A worked shape to check yourself against
```
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY app.py .
```
Copying `requirements.txt` and installing dependencies BEFORE copying the actual application
code means a code-only change never invalidates the (slow) dependency-install layer — only the
final `COPY app.py .` step needs to rerun.
## Instructor Notes
**Facilitation:** This is one of the most practically useful habits in the entire module — worth
connecting explicitly to real build-time savings on larger, slower dependency installs.
**Approximate timing:** 8-10 minutes live discussion; 5 minutes self-paced.
@@ -0,0 +1,24 @@
# Reflection: You Built a Real Image, Not Just Ran Someone Else's
## Reflection questions
1. Every image you used before this module (nginx, alpine) was built by someone else, from a
Dockerfile you never saw. Does writing your own change how you think about those images?
2. Which Dockerfile instruction do you expect to use the most in real application work?
## Real Ops takeaway
**A Dockerfile is executable documentation of exactly how an application's environment is
built — the same reproducibility promise ROOT 03's version control made about code, now applied
to environments.** You've closed the loop this course opened with Module 1's "it works on my
machine" problem: a Dockerfile means the environment itself is defined, versioned, and
rebuildable identically anywhere, by anyone, forever.
This module is complete. Continue to the next module.
## Instructor Notes
**Discussion questions for a live classroom:** question 1 is a good moment to reflect on the
"black box → understood mechanism" arc of this entire course.
**Approximate timing:** 5-8 minutes live discussion; 5 minutes self-paced writing.