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,65 @@
# Docker Compose
## Real applications are rarely just one container
A web app usually needs a database, maybe a cache, maybe a background worker — several
containers that need to be created, networked together, and started in a sensible order. Doing
this manually with individual `docker run` commands works, but gets tedious and error-prone fast,
especially remembering the exact flags every time.
## One file, the whole application
**Docker Compose** describes an entire multi-container application in one YAML file:
```yaml
services:
web:
image: nginx:alpine
ports:
- "8080:80"
depends_on:
- api
api:
image: my-api:1
```
- **`services`** — each entry is one container, using the exact same concepts (image, ports,
etc.) you already know from plain `docker run`.
- **`depends_on`** — controls STARTUP order (api starts before web) — note this ensures order,
not that the dependency is fully READY yet, a subtlety real applications often need extra
handling for.
- **Networking is automatic** — Compose creates a shared network for all services in the file by
default, so `web` can reach `api` by name immediately, no manual `docker network create`
needed.
## The commands
```
docker compose up -d # start every service defined in the file, in the background
docker compose ps # see the status of every service
docker compose logs # see logs from every service, or docker compose logs <service>
docker compose down # stop and remove everything Compose created
```
## Compose doesn't replace anything you've learned — it automates it
Every Compose service maps directly onto a `docker run` command you could write by hand; Compose
just describes the whole set declaratively in one file instead of remembering and re-typing
several long commands correctly, every time, in the right order.
## Instructor Notes
**Lesson goal:** Frame Compose as automation of already-understood concepts, not new magic —
students should be able to mentally translate a `services:` block back into individual
`docker run` commands.
**Approximate timing:** 15 minutes read; 10 minutes live.
**Common misconception to watch for:** treating `depends_on` as "waits until fully healthy" —
it only controls START order, not readiness; worth flagging as a real, common source of race
conditions in actual production Compose files.
**Discussion question:** "Why might describing your application declaratively in one file be
better than a shell script that runs several `docker run` commands in sequence?" (Good answer:
reviewable, versionable, and Compose itself understands the relationships between services,
rather than just blindly executing a sequence.)
@@ -0,0 +1,64 @@
# Demonstration: One File, Two Containers, One Network — Automatically
## OBSERVE
```yaml
services:
web:
image: nginx:alpine
ports:
- "8080:80"
cache:
image: redis:alpine
```
## HYPOTHESIS
Predict: without running `docker network create` yourself, will `web` be able to reach `cache`
by name after `docker compose up`?
## INSPECT
```
$ docker compose up -d
$ docker compose exec web sh -c "wget -qO- http://cache:6379/ 2>&1 | head -1"
```
(Redis doesn't speak HTTP, so this specific request looks odd, but the connection ITSELF
succeeding or failing tells you whether name resolution and network reachability work — a
`Connection refused` here would mean it REACHED `cache` but Redis rejected the protocol
mismatch, which is actually proof the network worked.)
## CHANGE
```
$ docker compose ps
NAME IMAGE STATUS
web-1 nginx:alpine Up
cache-1 redis:alpine Up
$ docker network ls | grep compose
xyz123 myproject_default bridge
```
Compose created a network automatically, named after the project directory, with no manual step.
## VERIFY
```
$ docker compose down
$ docker network ls | grep compose
(nothing)
```
`docker compose down` cleaned up EVERYTHING Compose created — containers AND the network — in
one command.
## Instructor Notes
**Demo goal:** Show the automatic networking as a real, observable fact (a real network exists,
named and scoped to the project) rather than an assumed convenience.
**Approximate timing:** 8 minutes live.
**Common mistake to watch for:** forgetting `docker compose down` and accumulating orphaned
networks/containers across multiple exercises — a good habit to reinforce explicitly.
@@ -0,0 +1,40 @@
# Knowledge Check: Docker Compose
---
**1. What does `depends_on` actually guarantee — and NOT guarantee?**
*Answer:* It guarantees START order (the dependency's container is started before the dependent
service's). It does NOT guarantee the dependency is actually READY/healthy yet — a real,
common source of race conditions in production Compose files.
---
**2. Do you need to manually run `docker network create` for services in the same Compose file
to reach each other by name?**
*Answer:* No — Compose automatically creates a shared network for all services defined in the
file, and they can reach each other by service name immediately.
---
**3. What does `docker compose down` clean up, that plain `docker compose stop` would not?**
*Answer:* `down` removes the containers AND the network Compose created; `stop` only stops the
containers, leaving them (and the network) in place to be restarted later.
---
**4. Does Compose introduce any concept that isn't already just automating `docker run`/
`docker network` commands you already know?**
*Answer:* No — every Compose service maps directly onto concepts already covered
(image, ports, networking); Compose automates and declaratively describes them, it doesn't add
new underlying mechanisms.
## Instructor Notes
**Facilitation:** Question 1 is worth real emphasis — it's a genuine, common production
mistake worth preventing early.
**Approximate timing:** 5 minutes.
@@ -0,0 +1,36 @@
# Guided Exercise: Translate a Compose File Back Into Plain `docker run`
This is a paper exercise — commit to your translation before checking it in the Lab.
## Your task
Given this Compose service definition, write the equivalent plain `docker run` command by hand:
```yaml
services:
web:
image: nginx:alpine
ports:
- "8080:80"
```
Then explain: what would you ALSO need to do manually (that Compose does automatically) if two
services needed to reach each other by name?
## A worked shape to check yourself against
```
docker run -d -p 8080:80 nginx:alpine
```
For two services to reach each other by name manually, you'd need to `docker network create` a
shared network yourself, then add `--network <that-network>` to EACH container's `docker run`
command — exactly the manual step Compose eliminates.
## Instructor Notes
**Facilitation:** This exercise is the clearest possible proof that Compose isn't new magic —
being able to translate it back to `docker run` confirms genuine understanding, not just
familiarity with YAML syntax.
**Approximate timing:** 8-10 minutes live discussion; 5 minutes self-paced.
@@ -0,0 +1,26 @@
# Reflection: A Real Multi-Service Application, Described and Running
## Reflection questions
1. Before this module, how would you have run two related containers and connected them
manually? How does that compare to this module's Lab?
2. What does a Compose file's `services:` block remind you of, from ROOT_'s later Kubernetes
content — even without knowing Kubernetes syntax yet?
## Real Ops takeaway
**You just described and ran a real multi-container application declaratively — the exact same
"describe what you want, let the tool make it real" pattern behind every later Kubernetes manifest
in this program.** This isn't a coincidence: Compose and Kubernetes both solve "orchestrate
multiple containers as one application," at different scales. The habit of thinking in terms of
declared services rather than manual step-by-step commands is what ROOT 05 builds on directly.
This module is complete. Continue to the final module of this course.
## Instructor Notes
**Discussion questions for a live classroom:** question 2 previews Kubernetes manifests
directly — a good moment to name "declarative description of desired state" as a term students
will hear constantly starting in ROOT 05.
**Approximate timing:** 5-8 minutes live discussion; 5 minutes self-paced writing.