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:
+58
@@ -0,0 +1,58 @@
|
||||
# Container Networking
|
||||
|
||||
## This module is ROOT 02's networking model, one layer up
|
||||
|
||||
Every concept here is a direct application of ROOT 02 Module 3's host-to-application chain —
|
||||
just with a container standing in for "host."
|
||||
|
||||
## Every container gets its own IP
|
||||
|
||||
By default, Docker gives each container its own IP address on a private, internal Docker
|
||||
network — exactly like a real host has an IP on a real network (ROOT 02 Module 3). Containers on
|
||||
the SAME Docker network can reach each other directly by that IP.
|
||||
|
||||
## Container-to-container DNS: reach a container by NAME
|
||||
|
||||
Docker runs its own internal DNS for user-defined networks — containers on the same network can
|
||||
resolve each other by CONTAINER NAME, not just IP. If a `web` container needs to reach an `api`
|
||||
container, it can simply connect to hostname `api` — Docker resolves it automatically. This is
|
||||
the exact "getent hosts" concept from ROOT 02, just automated for you.
|
||||
|
||||
## Publishing a port: the host/container boundary
|
||||
|
||||
A container's ports are, by default, only reachable from OTHER containers on the same Docker
|
||||
network — NOT from the host machine, and NOT from outside. **Publishing** a port
|
||||
(`-p hostPort:containerPort`) explicitly creates a mapping: traffic arriving at the HOST's
|
||||
`hostPort` gets forwarded to the CONTAINER's `containerPort`. This is set at container CREATION
|
||||
time — you cannot add it to an already-running container; it must be recreated.
|
||||
|
||||
```
|
||||
docker run -d -p 8080:80 nginx:alpine
|
||||
```
|
||||
|
||||
Host port 8080 → this container's port 80. `curl http://localhost:8080/` from the host now
|
||||
reaches it; `curl http://localhost:80/` from the host does NOT (the host's own port 80 was never
|
||||
touched).
|
||||
|
||||
## A working app that's still unreachable is a real, common failure
|
||||
|
||||
Exactly like ROOT 02's networking-diagnosis modules: a container can be perfectly healthy
|
||||
internally (`docker exec <container> curl localhost:80` succeeds) while being completely
|
||||
unreachable from the host, because publishing was never configured. This is one of the single
|
||||
most common container networking mistakes — and this module's Lab makes you diagnose and fix
|
||||
it for real.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Lesson goal:** Explicitly connect every idea here back to ROOT 02's networking model — this
|
||||
module should feel like recognition, not new material, reinforcing the curriculum's continuity.
|
||||
|
||||
**Approximate timing:** 15 minutes read; 10 minutes live.
|
||||
|
||||
**Common misconception to watch for:** assuming a container's port is automatically reachable
|
||||
from the host just because the container itself works fine — this exact misconception is what
|
||||
this module's Lab is built to correct through direct experience.
|
||||
|
||||
**Discussion question:** "Why might Docker default to NOT exposing container ports to the host
|
||||
automatically?" (Good answer: explicit opt-in is safer — you don't want every container's every
|
||||
port accidentally reachable from outside by default.)
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
# Demonstration: Reaching a Container By Name, Not IP
|
||||
|
||||
## OBSERVE
|
||||
|
||||
```
|
||||
$ docker network create demo-net
|
||||
$ docker run -d --name api-server --network demo-net nginx:alpine
|
||||
```
|
||||
|
||||
## HYPOTHESIS
|
||||
|
||||
Predict: can another container on `demo-net` reach `api-server` using its NAME as a hostname,
|
||||
without knowing its IP?
|
||||
|
||||
## INSPECT
|
||||
|
||||
```
|
||||
$ docker run --rm --network demo-net alpine:3.19 sh -c "wget -qO- http://api-server:80/ | head -1"
|
||||
<!DOCTYPE html>
|
||||
```
|
||||
|
||||
Yes — Docker's built-in DNS resolved `api-server` (a container NAME) directly, no IP address
|
||||
needed anywhere in that command.
|
||||
|
||||
## CHANGE
|
||||
|
||||
Now try reaching it from a container that is NOT on the same network:
|
||||
|
||||
```
|
||||
$ docker run --rm alpine:3.19 sh -c "wget -qO- http://api-server:80/ 2>&1 | head -1"
|
||||
wget: bad address 'api-server'
|
||||
```
|
||||
|
||||
Name resolution fails entirely — the container isn't even on the same Docker network, so there's
|
||||
no DNS entry for it there.
|
||||
|
||||
## VERIFY
|
||||
|
||||
Container-to-container name resolution is scoped to a SPECIFIC Docker network, not global —
|
||||
exactly like ROOT 02's subnet concept: reachability depends on which network you're actually on.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Demo goal:** Show container-name DNS resolution as a real, working feature — and show it
|
||||
explicitly FAILING when containers aren't on the same network, reinforcing that this isn't
|
||||
magic, it's scoped.
|
||||
|
||||
**Approximate timing:** 8 minutes live.
|
||||
|
||||
**Common mistake to watch for:** assuming any two containers can always reach each other by name
|
||||
regardless of network — reinforce the "same network required" rule explicitly.
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
# Knowledge Check: Container Networking
|
||||
|
||||
---
|
||||
|
||||
**1. A container works fine when tested with `docker exec <container> curl localhost:80`, but
|
||||
`curl http://localhost:8080/` from the host fails. What's the most likely cause?**
|
||||
|
||||
*Answer:* The container was never started with a port-publish flag (`-p 8080:80`) mapping the
|
||||
host port to the container's port — the app works internally, but nothing connects the host to
|
||||
it.
|
||||
|
||||
---
|
||||
|
||||
**2. Can you add port publishing to an already-running container?**
|
||||
|
||||
*Answer:* No — port publishing is set at container creation time; you must remove and recreate
|
||||
the container with the correct `-p` flag.
|
||||
|
||||
---
|
||||
|
||||
**3. How can one container reach another by name instead of IP?**
|
||||
|
||||
*Answer:* If both containers are on the same user-defined Docker network, Docker's built-in DNS
|
||||
resolves the target container's NAME automatically — no IP address needed.
|
||||
|
||||
---
|
||||
|
||||
**4. Why does container-name resolution fail between two containers that AREN'T on the same
|
||||
Docker network?**
|
||||
|
||||
*Answer:* Docker's per-network DNS only knows about containers actually attached to that
|
||||
specific network — being on a different (or no) network means there's no DNS entry for the
|
||||
target container at all.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Question 1 directly previews this module's Lab — a good moment to build
|
||||
anticipation for it.
|
||||
|
||||
**Approximate timing:** 5 minutes.
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
# Guided Exercise: Diagnose Before You Recreate Anything
|
||||
|
||||
This is a paper exercise — commit to a diagnostic plan before working in the Lab.
|
||||
|
||||
## Your task
|
||||
|
||||
A container `payments-api` is reported as "unreachable" by a teammate. Before touching anything,
|
||||
write out, in order, the exact commands you'd run to determine WHERE the problem actually is —
|
||||
using this module's and ROOT 02's tools.
|
||||
|
||||
## A worked shape to check yourself against
|
||||
|
||||
```
|
||||
docker ps --filter name=payments-api # is it even running?
|
||||
docker exec payments-api curl -sf localhost:PORT/ # does it work internally?
|
||||
docker port payments-api # is anything actually published?
|
||||
curl -sf http://localhost:PUBLISHED_PORT/ # does the published mapping actually work?
|
||||
```
|
||||
|
||||
This sequence isolates each layer independently — running? working internally? published at
|
||||
all? published mapping actually functional? — exactly the same "isolate one variable at a time"
|
||||
discipline ROOT 02 Module 3 taught, applied here to containers specifically.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** This exercise is deliberately a direct copy of ROOT 02's diagnostic discipline
|
||||
— the value is students recognizing that connection themselves, not being told it explicitly.
|
||||
|
||||
**Approximate timing:** 8-10 minutes live discussion; 5 minutes self-paced.
|
||||
@@ -0,0 +1,25 @@
|
||||
# Reflection: Same Diagnosis, New Layer
|
||||
|
||||
## Reflection questions
|
||||
|
||||
1. How much of this module's Lab did you solve using pure Docker knowledge, versus reasoning
|
||||
you already had from ROOT 02?
|
||||
2. Where do you expect "published port" reasoning to matter again later in ROOT 05+?
|
||||
|
||||
## Real Ops takeaway
|
||||
|
||||
**"Works internally, unreachable externally" is one of the single most common real-world
|
||||
container networking incidents — and you just diagnosed and fixed a real one.** The exact
|
||||
reasoning you used here (confirm internal health, check what's actually published, verify the
|
||||
published mapping works) is the same reasoning you'll use again for Kubernetes Services later —
|
||||
"published port" becomes "Service," but the underlying question (is anything actually routing
|
||||
traffic to this workload?) doesn't change.
|
||||
|
||||
This module is complete. Continue to the next module.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Discussion questions for a live classroom:** question 2 previews Kubernetes Services directly
|
||||
— worth naming that connection explicitly without teaching Services yet.
|
||||
|
||||
**Approximate timing:** 5-8 minutes live discussion; 5 minutes self-paced writing.
|
||||
Reference in New Issue
Block a user