Compare commits
6
Commits
b6516a74db
...
87ad1251ce
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
87ad1251ce | ||
|
|
59edceea1f | ||
|
|
a09b7aaa53 | ||
|
|
682ef2de47 | ||
|
|
e68b6f445a | ||
|
|
e007bcf000 |
+64
@@ -0,0 +1,64 @@
|
||||
# Linux System Administration
|
||||
|
||||
## Five questions every administrator asks
|
||||
|
||||
Whenever you're handed an unfamiliar running system, you're really always answering the same
|
||||
five questions: **What is running? Who started it? Where is its configuration? Where are its
|
||||
logs? How do I verify its state?** This module builds the vocabulary and tools for all five.
|
||||
|
||||
## Packages: how software gets installed
|
||||
|
||||
A **package manager** (`apt` on Debian/Ubuntu, `dnf`/`yum` on Fedora/RHEL) installs, updates, and
|
||||
removes software along with tracking what depends on what — the alternative (manually downloading
|
||||
and compiling everything) is exactly the kind of unreproducible mess package managers exist to
|
||||
prevent. You won't install packages by hand often in this Lab, but recognizing `apt install` /
|
||||
`apt list --installed` matters for reading real systems.
|
||||
|
||||
## Services: long-running background programs
|
||||
|
||||
A **service** is a program meant to run continuously in the background, outliving any single
|
||||
terminal session — a web server, a database, a log collector. Unlike a command you run and wait
|
||||
for, a service starts, keeps running, and is expected to still be there when you come back.
|
||||
|
||||
## systemd and systemctl (the concept — not available in this Lab)
|
||||
|
||||
On most real Linux servers, **systemd** is the process manager (PID 1) responsible for starting,
|
||||
stopping, and supervising services, and `systemctl` is the command you'd use to control it
|
||||
(`systemctl status nginx`, `systemctl restart nginx`, `journalctl -u nginx` for its logs).
|
||||
**This disposable Lab container does not run systemd** — its PID 1 is a boot script that starts
|
||||
k3s directly, the same way a real container almost never runs a full init system. This is worth
|
||||
knowing honestly rather than pretending otherwise: the CONCEPTS below (a running process, its
|
||||
config file, its logs, verifying its state) are identical whether the supervisor is systemd or
|
||||
something else — this module's Labs teach those concepts against a service you CAN directly
|
||||
inspect and control in this environment.
|
||||
|
||||
## Configuration files and environment variables
|
||||
|
||||
Most services read their settings from either a config file (a specific path, usually under
|
||||
`/etc/`) or **environment variables** (`env` lists every variable in your current shell;
|
||||
`export NAME=value` sets one). Knowing WHERE a service's configuration lives is exactly the
|
||||
"where is its configuration" question from this lesson's opening — you'll answer it for real in
|
||||
this module's first Lab.
|
||||
|
||||
## Logs: where a service tells you what happened
|
||||
|
||||
A well-behaved service writes what it's doing to a log — usually a file under `/var/log/`.
|
||||
Reading logs (with `cat`, `tail`, `tail -f` — from ROOT 01) is almost always the first real step
|
||||
in figuring out why something isn't working, which Module 6 builds into a full framework.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Lesson goal:** Establish the five-question mental model (running/started-by/config/logs/
|
||||
verify) as the organizing frame for the rest of this module, and be explicit and honest that
|
||||
systemd isn't available in this specific Lab environment rather than silently working around it.
|
||||
|
||||
**Approximate timing:** 15 minutes read; 10 minutes live.
|
||||
|
||||
**Common misconception to watch for:** students who've used systemd elsewhere may expect
|
||||
`systemctl` to exist here — address this directly and early rather than letting them discover
|
||||
the error on their own.
|
||||
|
||||
**Discussion question:** "If you SSH into an unfamiliar production server and need to figure out
|
||||
what's running and whether it's healthy, what's the very first command you'd run?" (There's no
|
||||
single right answer — the point is getting students to articulate their own process before this
|
||||
module hands them formal tools for it.)
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
# Demonstration: Answering the Five Questions For Real
|
||||
|
||||
## OBSERVE
|
||||
|
||||
This Lab's own terminal is served by a real running service: nginx.
|
||||
|
||||
```
|
||||
$ pgrep -x nginx
|
||||
906
|
||||
```
|
||||
|
||||
Something with PID 906 is running.
|
||||
|
||||
## HYPOTHESIS
|
||||
|
||||
Predict: where would nginx's configuration file be, and where would its logs be?
|
||||
|
||||
## INSPECT
|
||||
|
||||
```
|
||||
$ cat /etc/nginx/nginx.conf | head -5
|
||||
worker_processes 1;
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
pid /run/nginx.pid;
|
||||
...
|
||||
$ ls /var/log/nginx/
|
||||
access.log error.log
|
||||
```
|
||||
|
||||
## CHANGE
|
||||
|
||||
Confirm it's actually listening on the port you'd expect:
|
||||
|
||||
```
|
||||
$ ss -lnt | grep :80
|
||||
LISTEN 0 511 0.0.0.0:80 0.0.0.0:*
|
||||
```
|
||||
|
||||
## VERIFY
|
||||
|
||||
All five questions answered with real evidence, not assumptions: it's running (PID 906),
|
||||
listening on port 80, configured at `/etc/nginx/nginx.conf`, logging to `/var/log/nginx/`. This
|
||||
exact sequence — process, port, config, logs — is what you'll repeat for almost any unfamiliar
|
||||
service on any real system.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Demo goal:** Show the five-question checklist answered end-to-end against a real, already-
|
||||
running service the students are themselves using right now — more convincing than an
|
||||
abstract example.
|
||||
|
||||
**Approximate timing:** 8 minutes live.
|
||||
|
||||
**Common mistake to watch for:** stopping here at "PID exists" without also confirming it's
|
||||
actually listening on the expected port — a process existing and a service actually being
|
||||
reachable are two different facts, both worth checking.
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
# Knowledge Check: Linux System Administration
|
||||
|
||||
---
|
||||
|
||||
**1. What are the five questions this module opened with?**
|
||||
|
||||
*Answer:* What is running? Who started it? Where is its configuration? Where are its logs? How
|
||||
do I verify its state?
|
||||
|
||||
---
|
||||
|
||||
**2. Why doesn't this Lab environment have `systemctl`?**
|
||||
|
||||
*Answer:* This Lab's container doesn't run systemd as PID 1 — its boot script starts k3s
|
||||
directly instead, the same way most containers don't run a full init system. The concepts
|
||||
(inspecting a running service's process/config/logs/state) are the same regardless of which
|
||||
supervisor manages it.
|
||||
|
||||
---
|
||||
|
||||
**3. A process exists (`pgrep` finds a PID) but the service seems unreachable. What's the next
|
||||
thing to check?**
|
||||
|
||||
*Answer:* Whether it's actually listening on the expected port (`ss -lnt`) — a running process
|
||||
and a reachable service are two separate facts; the process could have started but failed to
|
||||
bind its port.
|
||||
|
||||
---
|
||||
|
||||
**4. Where does a well-behaved Linux service typically write what it's doing?**
|
||||
|
||||
*Answer:* A log file, usually under `/var/log/`.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Question 2 is worth confirming explicitly if any student has prior systemd
|
||||
experience — set the expectation before the Lab, not after a confusing error.
|
||||
|
||||
**Approximate timing:** 5 minutes.
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
# Guided Exercise: Plan Your Inspection Before You Type Anything
|
||||
|
||||
This is a paper exercise — commit to a plan before running commands in the Lab.
|
||||
|
||||
## Your task
|
||||
|
||||
You're told "the payments service seems slow." Before touching a keyboard, write down, in
|
||||
order, which of these five questions you'd check FIRST, SECOND, and so on, and the one command
|
||||
you'd use for each:
|
||||
|
||||
- Is it running at all?
|
||||
- Is it listening on its expected port?
|
||||
- What does its configuration say?
|
||||
- What do its recent logs say?
|
||||
- Has anything changed recently?
|
||||
|
||||
## A worked shape to check yourself against
|
||||
|
||||
A reasonable order: (1) is it running — `pgrep`/`ps`; (2) is it listening — `ss -lnt`; (3) recent
|
||||
logs — `tail -n 50 <logfile>` (recent activity is usually more informative than config for a
|
||||
sudden "slow" complaint); (4) configuration — only if logs point at a config-driven cause; (5)
|
||||
recent changes — depends on context (deployment history, `ls -la` timestamps on config files).
|
||||
There's no single correct order, but "running, then listening, then logs" before jumping to
|
||||
config is a defensible default — you're checking the cheapest, fastest-answering questions
|
||||
first.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** There's genuine room for disagreement on ordering here — the value is in
|
||||
students defending their reasoning, not converging on one "correct" sequence.
|
||||
|
||||
**Approximate timing:** 8-10 minutes live discussion; 5 minutes self-paced.
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
# Reflection: You Now Have a Repeatable Checklist
|
||||
|
||||
## Reflection questions
|
||||
|
||||
1. Before this module, if someone said "check whether the service is healthy," what would you
|
||||
have actually done? How does that compare to the five-question checklist now?
|
||||
2. You started and stopped a service you fully controlled this module. What's different about
|
||||
doing the same thing to a service you DIDN'T start, on a machine you don't fully understand
|
||||
yet?
|
||||
|
||||
## Real Ops takeaway
|
||||
|
||||
**The five-question checklist works on ANY unfamiliar Linux system, not just this Lab.** Running
|
||||
process, listening port, config location, logs, and current state — this is genuinely how
|
||||
experienced engineers approach a machine they've never touched before. You'll use this exact
|
||||
checklist again in ROOT 04 when a container "doesn't seem to be working," and again in
|
||||
Kubernetes when a Pod isn't behaving as expected — the tools change, the checklist doesn't.
|
||||
|
||||
This module is complete. Continue to the next module.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Discussion questions for a live classroom:** question 2 is worth real discussion time — the
|
||||
honest answer is "you should be more careful and inspect more before acting," a good early
|
||||
seed for the least-privilege/caution habits ROOT 09 formalizes much later.
|
||||
|
||||
**Approximate timing:** 5-8 minutes live discussion; 5 minutes self-paced writing.
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
# Storage and Filesystems
|
||||
|
||||
## Block devices and partitions, conceptually
|
||||
|
||||
A **block device** is the kernel's view of a storage device (a disk, an SSD, a cloud volume) —
|
||||
`lsblk` lists them. A device can be split into **partitions**, separate sections each formatted
|
||||
with their own filesystem, though a single-partition disk is common too. You won't partition
|
||||
anything in this Lab, but recognizing `lsblk`'s output (`sda`, `sda1`, `nvme0n1`...) matters for
|
||||
reading real systems.
|
||||
|
||||
## Filesystems and mount points
|
||||
|
||||
A **filesystem** is the format that organizes raw blocks into the files/directories you actually
|
||||
interact with (ext4, xfs, and — inside this container — overlay, a filesystem UNIQUE to how
|
||||
containers layer their storage). A filesystem becomes usable by being **mounted** at some
|
||||
directory path — `/` itself is a mount point, and additional filesystems can be mounted at other
|
||||
paths (a separate disk mounted at `/data`, for example).
|
||||
|
||||
## Persistent vs. temporary storage — a distinction that matters immediately
|
||||
|
||||
Not everything written to disk is meant to last. `/tmp` is conventionally temporary (ROOT 01);
|
||||
a container's own writable layer is, by convention and by ROOT 04's design, meant to be
|
||||
DISPOSABLE — data written inside a container that isn't explicitly persisted elsewhere disappears
|
||||
when that container is destroyed. This distinction — is this data supposed to survive, or not —
|
||||
is the single most important storage question you'll carry forward into ROOT 04's volumes and
|
||||
eventually Kubernetes' PersistentVolumes.
|
||||
|
||||
## Measuring usage: `df` vs. `du`
|
||||
|
||||
These answer two DIFFERENT questions, a common point of confusion:
|
||||
|
||||
- **`df -h`** — how much space is used/free on an entire mounted FILESYSTEM.
|
||||
- **`du -sh <path>`** — how much space one specific DIRECTORY's contents actually use.
|
||||
|
||||
A filesystem can show plenty of free space overall (`df`) while one specific directory has
|
||||
unexpectedly ballooned (`du`) — you need both questions answered to diagnose "why is disk full."
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Lesson goal:** Build the persistent-vs-temporary distinction explicitly, since it's the direct
|
||||
conceptual root of ROOT 04's bind-mount/volume distinction and, later, Kubernetes storage.
|
||||
|
||||
**Approximate timing:** 12 minutes read; 8 minutes live.
|
||||
|
||||
**Common misconception to watch for:** conflating `df` and `du` — a student surprised that
|
||||
`df` doesn't show them "which directory is huge" is a good moment to reinforce they answer
|
||||
different questions.
|
||||
|
||||
**Demo idea:** `df -h /` then `du -sh /var/log/*` side by side, narrating the different
|
||||
question each answers.
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
# Demonstration: Two Different Questions, Two Different Commands
|
||||
|
||||
## OBSERVE
|
||||
|
||||
```
|
||||
$ df -h /
|
||||
Filesystem Size Used Avail Use% Mounted on
|
||||
overlay 937G 199G 691G 23% /
|
||||
```
|
||||
|
||||
The root filesystem overall: 23% used, plenty free.
|
||||
|
||||
## HYPOTHESIS
|
||||
|
||||
Predict: does this tell you anything about which specific directory is using the most space?
|
||||
|
||||
## INSPECT
|
||||
|
||||
```
|
||||
$ du -sh /var/log/* 2>/dev/null
|
||||
1.2M /var/log/nginx
|
||||
850K /var/log/k3s.log
|
||||
```
|
||||
|
||||
No — `df` only ever answers "how full is this filesystem overall." Finding a SPECIFIC large
|
||||
consumer requires `du`, pointed at the directories you suspect.
|
||||
|
||||
## CHANGE
|
||||
|
||||
Narrow further into a single suspicious directory:
|
||||
|
||||
```
|
||||
$ du -sh /var/log/nginx/*
|
||||
600K /var/log/nginx/access.log
|
||||
600K /var/log/nginx/error.log
|
||||
```
|
||||
|
||||
## VERIFY
|
||||
|
||||
You now know both the overall filesystem picture (`df`) AND exactly which files are contributing
|
||||
to it (`du`, narrowed progressively) — two genuinely different tools for two genuinely different
|
||||
questions, not interchangeable.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Demo goal:** Make the df/du distinction concrete by actually needing BOTH to answer a
|
||||
realistic "what's using my disk" question.
|
||||
|
||||
**Approximate timing:** 8 minutes live.
|
||||
|
||||
**Common mistake to watch for:** running `du -sh /` directly (extremely slow on a large
|
||||
filesystem, since it recurses everything) instead of narrowing progressively into suspected
|
||||
directories first.
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
# Knowledge Check: Storage and Filesystems
|
||||
|
||||
---
|
||||
|
||||
**1. `df -h /` shows only 23% used, but you suspect one directory is unusually large. Does
|
||||
`df` alone answer that?**
|
||||
|
||||
*Answer:* No — `df` reports overall filesystem usage, not per-directory. You need `du -sh <path>`
|
||||
pointed at the suspect directory (or directories) to answer that.
|
||||
|
||||
---
|
||||
|
||||
**2. What makes data in a container's writable layer different from data in `/home`?**
|
||||
|
||||
*Answer:* By convention and design, a container's own writable layer is meant to be disposable —
|
||||
data there disappears when the container is destroyed, unless explicitly persisted elsewhere
|
||||
(a topic ROOT 04 covers directly). `/home` data is expected to persist normally.
|
||||
|
||||
---
|
||||
|
||||
**3. What does "mounting" a filesystem actually mean?**
|
||||
|
||||
*Answer:* Attaching a filesystem so it becomes accessible at a specific directory path — `/`
|
||||
itself is a mount point, and additional filesystems can be mounted at other paths.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Question 2 previews ROOT 04's volume/bind-mount content directly — worth
|
||||
naming that connection explicitly.
|
||||
|
||||
**Approximate timing:** 5 minutes.
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
# Guided Exercise: Narrow Down a Disk Problem on Paper
|
||||
|
||||
This is a paper exercise — commit to a plan before running commands in the Lab.
|
||||
|
||||
## Your task
|
||||
|
||||
`df -h /` shows the root filesystem is 95% full. You have three suspect directories:
|
||||
`/var/log`, `/srv/cache`, and `/home`. Write the exact sequence of `du` commands you'd run to
|
||||
find the actual culprit, narrowing your search efficiently rather than checking everything with
|
||||
maximum depth immediately.
|
||||
|
||||
## A worked shape to check yourself against
|
||||
|
||||
```
|
||||
du -sh /var/log /srv/cache /home
|
||||
```
|
||||
|
||||
— one command, comparing all three top-level suspects at once. Whichever is largest, THEN
|
||||
narrow further into it specifically (e.g. `du -sh /srv/cache/*` if that one's the culprit) rather
|
||||
than deep-diving into all three from the start — this mirrors Module 1's "cheapest, fastest-
|
||||
answering question first" principle.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Point out that running `du -sh` separately three times versus once with all
|
||||
three paths produces the same information — the point isn't syntax, it's the "compare broadly,
|
||||
then narrow" strategy.
|
||||
|
||||
**Approximate timing:** 8-10 minutes live discussion; 5 minutes self-paced.
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
# Reflection: "It's Probably the Logs" Is a Guess, Not a Diagnosis
|
||||
|
||||
## Reflection questions
|
||||
|
||||
1. Before measuring, which of the three directories in this module's Lab did you guess would
|
||||
be largest? Were you right?
|
||||
2. Why does "persistent vs. temporary" matter enough to be worth memorizing as a question you
|
||||
ask about EVERY piece of data, not just during a storage module?
|
||||
|
||||
## Real Ops takeaway
|
||||
|
||||
**Guessing which directory is consuming disk space, without measuring, is exactly the kind of
|
||||
assumption that wastes real incident-response time.** `du`, applied progressively (broad, then
|
||||
narrow), replaces guessing with evidence in seconds. And the persistent-vs-temporary question
|
||||
you just practiced is the exact same question you'll ask again in ROOT 04 about container data,
|
||||
and again much later about Kubernetes storage — a question worth internalizing now, cheaply,
|
||||
rather than learning the hard way after data you assumed would persist didn't.
|
||||
|
||||
This module is complete. Continue to the next module.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Discussion questions for a live classroom:** question 1 works well as a quick poll before
|
||||
revealing the real answer — the mismatch between guesses and reality reinforces the lesson
|
||||
better than stating it directly.
|
||||
|
||||
**Approximate timing:** 5-8 minutes live discussion; 5 minutes self-paced writing.
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
# Networking Mental Model
|
||||
|
||||
## Why this module gets extra attention
|
||||
|
||||
Nearly everything you'll do for the rest of ROOT_ — container networking, Kubernetes Services,
|
||||
Ingress, NetworkPolicy, DNS, cloud infrastructure — is a variation on the SAME small set of
|
||||
ideas this module introduces. Get this model solid now, and every later networking topic becomes
|
||||
"the same thing again, one layer up." Skip it, and every later module will feel like memorizing
|
||||
unrelated facts. The goal here is operational REASONING, not subnet arithmetic — you will not be
|
||||
asked to calculate a subnet mask by hand.
|
||||
|
||||
## The chain, layer by layer
|
||||
|
||||
Think of reaching another machine as passing through a fixed sequence of layers, each of which
|
||||
can independently succeed or fail:
|
||||
|
||||
```
|
||||
Host
|
||||
↓
|
||||
Network interface (this machine's connection to a network)
|
||||
↓
|
||||
IP address (this machine's address on that network)
|
||||
↓
|
||||
Subnet (which other addresses are "local" to reach directly)
|
||||
↓
|
||||
Default gateway (where to send anything NOT local)
|
||||
↓
|
||||
Routing (the actual path traffic takes)
|
||||
↓
|
||||
DNS (translating a name into an IP address)
|
||||
↓
|
||||
Port (which specific service on that IP)
|
||||
↓
|
||||
TCP / UDP (how the connection itself behaves)
|
||||
↓
|
||||
Application protocol (HTTP, etc. — the actual conversation)
|
||||
```
|
||||
|
||||
## Each layer, briefly
|
||||
|
||||
- **Network interface** — your machine's actual network connection (`eth0`, etc.). No interface,
|
||||
no networking, full stop.
|
||||
- **IP address** — this machine's address on a given network. IPv4 addresses look like
|
||||
`172.17.0.2`. `127.0.0.1` (aka `localhost`) is special: it always means "this same machine,"
|
||||
never anywhere else. `0.0.0.0` is different again — when a SERVICE binds to it, it means
|
||||
"listen on every interface this machine has," not a real destination address.
|
||||
- **Subnet** — a range of addresses considered "local" to each other, expressed as CIDR
|
||||
(`172.17.0.0/16` means every address starting `172.17.`). You don't need to calculate subnet
|
||||
math by hand — you need to recognize that two addresses in the same subnet can talk directly,
|
||||
while addresses outside it need a gateway.
|
||||
- **Default gateway** — the address your machine sends traffic to when the destination ISN'T in
|
||||
its own subnet — effectively "ask this router to figure out the rest."
|
||||
- **Routing** — the actual decision-making about which path traffic takes; `ip route` shows your
|
||||
machine's routing table (Module 4 makes this hands-on).
|
||||
- **DNS** — translates a human-readable name (`example.com`) into an IP address, since IP
|
||||
addresses aren't what humans want to remember or type.
|
||||
- **Port** — a single IP address can host MANY services simultaneously; the port number is what
|
||||
distinguishes "which one." Port 80 conventionally means HTTP, 443 HTTPS, 22 SSH — conventions,
|
||||
not hard rules.
|
||||
- **TCP vs. UDP** — TCP is connection-oriented and guarantees delivery/order (most web traffic,
|
||||
SSH); UDP is connectionless and faster but delivers no guarantees (DNS queries themselves,
|
||||
video streaming). Client/server: one side (the server) listens on a fixed port waiting for
|
||||
connections; the other (the client) initiates them.
|
||||
|
||||
## The reasoning skill: "where is the failure?"
|
||||
|
||||
Given this chain, a real diagnostic question decomposes into a sequence of yes/no checks, each
|
||||
isolating ONE layer:
|
||||
|
||||
- Does the host have an IP address at all?
|
||||
- Is the destination local (same subnet) or remote?
|
||||
- Is there a valid route to it?
|
||||
- Can the name resolve to an IP?
|
||||
- Can the destination PORT actually be reached?
|
||||
- Is a service actually listening there?
|
||||
- Does the application itself respond correctly?
|
||||
|
||||
Module 4 makes each of these a real command. Module 5 walks the complete request path end to
|
||||
end. Module 6 formalizes this into RootNode's troubleshooting framework, applied to networking
|
||||
specifically.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Lesson goal:** This module gets outsized weight for a reason — it's the single highest-
|
||||
leverage conceptual investment in ROOT 02. Do not rush it to "cover the topics"; depth here pays
|
||||
off across ROOT 04, ROOT 05-10, and beyond.
|
||||
|
||||
**Approximate timing:** 25 minutes read; 15-20 minutes live with discussion — longer than a
|
||||
typical lesson, deliberately.
|
||||
|
||||
**Common misconception to watch for:** treating `0.0.0.0` as a real destination address rather
|
||||
than a "listen on every interface" bind convention — this genuinely confuses students later when
|
||||
they see it in a container's own listening-port output.
|
||||
|
||||
**Discussion question:** Walk through a real failure narrative live: "You `curl` a URL and get
|
||||
'Connection refused.' Which layers does that rule OUT, and which remain suspects?" (Answer:
|
||||
rules out DNS entirely — you got FAR enough to attempt a TCP connection — and rules out "nothing
|
||||
is listening at all" being the ENTIRE problem elsewhere; "refused" specifically means something
|
||||
actively rejected the connection, most often nothing listening on that exact port.)
|
||||
|
||||
**Optional extension:** contrast "Connection refused" vs. "Connection timed out" vs. "Could not
|
||||
resolve host" as three DIFFERENT failure signatures pointing at different layers — worth a full
|
||||
discussion pass if time allows, since Module 5 depends on students already sensing this
|
||||
distinction.
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
# Demonstration: Three Failures That Sound Similar But Aren't
|
||||
|
||||
## OBSERVE
|
||||
|
||||
All three of these LOOK like "it didn't work" — but each one rules out and rules in completely
|
||||
different layers.
|
||||
|
||||
```
|
||||
$ curl http://this-name-does-not-resolve.invalid/
|
||||
curl: (6) Could not resolve host: this-name-does-not-resolve.invalid
|
||||
```
|
||||
|
||||
## HYPOTHESIS
|
||||
|
||||
Which layer does THIS failure point at — DNS, routing, the port, or the application itself?
|
||||
|
||||
## INSPECT
|
||||
|
||||
"Could not resolve host" happens BEFORE any network connection is even attempted — it's purely a
|
||||
DNS failure. No IP was ever found to connect to.
|
||||
|
||||
## CHANGE
|
||||
|
||||
Now a different failure, against a name that DOES resolve:
|
||||
|
||||
```
|
||||
$ curl http://localhost:9999/
|
||||
curl: (7) Failed to connect to localhost port 9999: Connection refused
|
||||
```
|
||||
|
||||
DNS/name resolution already succeeded here (`localhost` always resolves). "Connection refused"
|
||||
means a TCP packet reached the destination and was ACTIVELY rejected — almost always because
|
||||
nothing is listening on that port.
|
||||
|
||||
## VERIFY
|
||||
|
||||
A third signature, distinct from both:
|
||||
|
||||
```
|
||||
$ curl http://10.255.255.1:80/
|
||||
curl: (28) Failed to connect to 10.255.255.1 port 80: Connection timed out
|
||||
```
|
||||
|
||||
No response at all, not even a rejection — usually means the destination is unreachable
|
||||
(no route, or blocked silently), not that something is running but declining you.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Demo goal:** Get students to viscerally distinguish these three error MESSAGES as three
|
||||
different diagnostic starting points, not interchangeable "it's broken" signals.
|
||||
|
||||
**Approximate timing:** 10 minutes live.
|
||||
|
||||
**Common mistake to watch for:** treating every connection failure as "the network is down" —
|
||||
each of these three messages should immediately narrow which layer to investigate next, per
|
||||
Module 5's troubleshooting exercise.
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
# Knowledge Check: Networking Mental Model
|
||||
|
||||
---
|
||||
|
||||
**1. What's the actual difference between an IP address and a port?**
|
||||
|
||||
*Answer:* An IP address identifies a specific machine on a network; a port identifies WHICH
|
||||
service on that machine, since one IP can host many services simultaneously.
|
||||
|
||||
---
|
||||
|
||||
**2. `curl` reports "Could not resolve host." Which layer failed?**
|
||||
|
||||
*Answer:* DNS — the name never got translated into an IP address, so no connection attempt was
|
||||
even made.
|
||||
|
||||
---
|
||||
|
||||
**3. `curl` reports "Connection refused" instead. What does that specifically tell you?**
|
||||
|
||||
*Answer:* The name resolved fine and the destination was reachable, but nothing is listening on
|
||||
the port you tried — the connection attempt was actively rejected, not silently ignored.
|
||||
|
||||
---
|
||||
|
||||
**4. What is a default gateway for?**
|
||||
|
||||
*Answer:* It's where your machine sends traffic whose destination ISN'T in its own local subnet
|
||||
— effectively "ask this router to figure out how to get there."
|
||||
|
||||
---
|
||||
|
||||
**5. True or false: `0.0.0.0` is a real destination address you could `curl`.**
|
||||
|
||||
*Answer:* False (in the way most students initially assume) — when a SERVICE binds to
|
||||
`0.0.0.0`, it means "listen on every interface," a bind convention, not a real host you'd
|
||||
connect to as a destination.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Questions 2-3 are worth spending real time on together — the point isn't
|
||||
memorizing the exact error text, it's the underlying "which layer does this rule out" reasoning.
|
||||
|
||||
**Approximate timing:** 8 minutes (slightly longer than a typical knowledge check, given this
|
||||
module's weight).
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
# Guided Exercise: Diagnose From the Symptom Alone
|
||||
|
||||
This is a paper exercise — reason through each case using only the mental model, before any
|
||||
hands-on Lab work.
|
||||
|
||||
## Your task
|
||||
|
||||
For each symptom, name which layer(s) it rules OUT, and which remain genuine suspects:
|
||||
|
||||
1. `ping 8.8.8.8` succeeds, but `curl https://example.com` fails with "Could not resolve host."
|
||||
2. `dig example.com` returns a correct-looking IP address, but `curl https://example.com` times
|
||||
out with no response at all.
|
||||
3. `curl http://internal-service:8080/` returns "Connection refused," but
|
||||
`curl http://internal-service:9090/` (a different port, same host) succeeds.
|
||||
|
||||
## A worked shape to check yourself against
|
||||
|
||||
1. Rules out: basic connectivity/routing to the internet (ping succeeded). Remaining suspect:
|
||||
DNS specifically — something is preventing name resolution even though raw IP connectivity
|
||||
works.
|
||||
2. Rules out: DNS (resolution clearly succeeded). Remaining suspects: routing/reachability to
|
||||
that specific IP, or a firewall silently dropping the connection — "no response at all" is the
|
||||
timeout signature, not the "actively rejected" signature.
|
||||
3. Rules out: DNS AND general reachability to that host (port 9090 worked). Remaining suspect:
|
||||
narrowed all the way down to "nothing is listening on port 8080 specifically" — the most
|
||||
precisely isolated diagnosis of the three.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Item 3 is the strongest example of "isolate one variable" reasoning (same
|
||||
host, different port) — worth highlighting explicitly as the gold-standard diagnostic pattern.
|
||||
|
||||
**Approximate timing:** 10-12 minutes live discussion; 8 minutes self-paced.
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
# Reflection: One Model, Reused Everywhere From Here On
|
||||
|
||||
## Reflection questions
|
||||
|
||||
1. Before this module, how would you have described "the network is broken" to a colleague?
|
||||
How would you describe it now, more precisely?
|
||||
2. This module deliberately avoided subnet math. Do you feel that cost you anything real, given
|
||||
the module's actual goal (reasoning about WHERE a failure is, not calculating addresses)?
|
||||
|
||||
## Real Ops takeaway
|
||||
|
||||
**Every networking topic for the rest of ROOT_ is this same chain, viewed from a different
|
||||
altitude.** A container's published port is still "port," a Kubernetes Service is still "how do
|
||||
I reach the right destination via a stable address," Ingress is still "routing decisions," and
|
||||
NetworkPolicy is still "which subnets/ports are allowed to reach which." You are not about to
|
||||
learn a new mental model in ROOT 04 or ROOT 05 — you're about to see this SAME one applied one
|
||||
layer up, repeatedly. That's why this module got extra weight: the investment compounds.
|
||||
|
||||
This module is complete. Continue to Module 4, where these ideas become real commands.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Discussion questions for a live classroom:** question 1 is worth collecting several answers
|
||||
before and after — the change in precision is itself the evidence this module worked.
|
||||
|
||||
**Approximate timing:** 8-10 minutes live discussion; 5 minutes self-paced writing.
|
||||
|
||||
**Milestone note:** this is the "critical module" this course was built around — consider
|
||||
confirming as a group that everyone can walk the full chain (host→interface→IP→subnet→gateway→
|
||||
routing→DNS→port→TCP/UDP→application) from memory before moving on.
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
# Linux Network Inspection
|
||||
|
||||
## Every command below is really a question
|
||||
|
||||
Module 3 gave you the model. This module gives you the commands — but treat each one as
|
||||
ANSWERING a specific question, not as syntax to memorize.
|
||||
|
||||
## "What addresses does this machine have?" — `ip addr`
|
||||
|
||||
```
|
||||
$ ip -4 addr show eth0
|
||||
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
|
||||
```
|
||||
|
||||
This machine's IPv4 address is `172.17.0.2`, on a `/16` subnet.
|
||||
|
||||
## "Where will this machine send traffic?" — `ip route`
|
||||
|
||||
```
|
||||
$ ip route
|
||||
default via 172.17.0.1 dev eth0
|
||||
172.17.0.0/16 dev eth0 proto kernel scope link src 172.17.0.2
|
||||
```
|
||||
|
||||
The second line says: anything in `172.17.0.0/16` is reachable directly. The first line (the
|
||||
`default` route) says: everything else goes through `172.17.0.1` — the default gateway from
|
||||
Module 3, now a real line in a real table.
|
||||
|
||||
## "What TCP ports are listening?" — `ss -lnt`
|
||||
|
||||
```
|
||||
$ ss -lnt
|
||||
LISTEN 0 511 0.0.0.0:80 0.0.0.0:*
|
||||
```
|
||||
|
||||
Something is listening on port 80, on every interface (`0.0.0.0` as a BIND address, exactly the
|
||||
convention Module 3 flagged). `ss` replaced the older `netstat` on modern Linux — same idea.
|
||||
|
||||
## "What does DNS say this name resolves to?" — `getent hosts` / `dig`
|
||||
|
||||
```
|
||||
$ getent hosts example.test
|
||||
10.0.0.5 example.test
|
||||
```
|
||||
|
||||
`getent hosts` checks the same resolution path the rest of the system uses (local `/etc/hosts`
|
||||
entries first, then DNS) — a reliable first choice. `dig <name>` queries DNS directly and shows
|
||||
more detail, useful once you specifically suspect a real DNS server issue rather than a local
|
||||
override.
|
||||
|
||||
## "Can the application actually respond?" — `curl`
|
||||
|
||||
```
|
||||
$ curl -sf http://localhost:80/ -o /dev/null -w '%{http_code}\n'
|
||||
200
|
||||
```
|
||||
|
||||
Everything before this point (address, route, port, DNS) can be perfect, and the APPLICATION
|
||||
itself can still be broken — `curl` is the only one of these tools that actually tests the full
|
||||
chain end to end, including the application's own response.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Lesson goal:** Convert Module 3's mental model into muscle-memory commands, each explicitly
|
||||
framed as answering one specific diagnostic question.
|
||||
|
||||
**Approximate timing:** 15 minutes read; 10 minutes live.
|
||||
|
||||
**Common misconception to watch for:** treating all five commands as redundant ways to "check
|
||||
the network" — reinforce that each answers a DIFFERENT layer's question, and a real diagnosis
|
||||
usually needs more than one.
|
||||
|
||||
**Demo idea:** run all five in sequence against this Lab's own nginx, narrating which Module 3
|
||||
layer each one answers.
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
# Demonstration: The Same Five Questions, Answered Live
|
||||
|
||||
## OBSERVE
|
||||
|
||||
```
|
||||
$ ip -4 addr show eth0
|
||||
inet 172.17.0.2/16 ...
|
||||
```
|
||||
|
||||
"What address do I have?" — answered.
|
||||
|
||||
## HYPOTHESIS
|
||||
|
||||
Predict what `ip route` will show before running it — specifically, predict whether there will
|
||||
be a `default` line.
|
||||
|
||||
## INSPECT
|
||||
|
||||
```
|
||||
$ ip route
|
||||
default via 172.17.0.1 dev eth0
|
||||
172.17.0.0/16 dev eth0 ...
|
||||
```
|
||||
|
||||
Confirmed — a `default` route exists, meaning traffic to anywhere outside `172.17.0.0/16` has
|
||||
somewhere to go.
|
||||
|
||||
## CHANGE
|
||||
|
||||
```
|
||||
$ ss -lnt
|
||||
LISTEN 0 511 0.0.0.0:80 0.0.0.0:*
|
||||
```
|
||||
|
||||
"What's listening?" — port 80, confirmed.
|
||||
|
||||
## VERIFY
|
||||
|
||||
```
|
||||
$ curl -sf http://localhost/ -o /dev/null -w '%{http_code}\n'
|
||||
200
|
||||
```
|
||||
|
||||
The full chain, verified end to end with real evidence at each layer — not "the network seems
|
||||
fine," but five specific, falsifiable answers.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Demo goal:** Chain all five commands together as ONE continuous diagnostic narrative, not
|
||||
five disconnected syntax examples.
|
||||
|
||||
**Approximate timing:** 8 minutes live.
|
||||
|
||||
**Common mistake to watch for:** running `curl` first and stopping at "it worked" without ever
|
||||
having separately confirmed address/route/port — fine when things work, but the WRONG habit for
|
||||
when they don't (Module 5's Lab requires the full sequence).
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
# Knowledge Check: Linux Network Inspection
|
||||
|
||||
---
|
||||
|
||||
**1. Which command answers "what TCP ports are listening on this machine"?**
|
||||
|
||||
*Answer:* `ss -lnt`.
|
||||
|
||||
---
|
||||
|
||||
**2. Which command answers "where will this machine send traffic that isn't local"?**
|
||||
|
||||
*Answer:* `ip route` — specifically, the `default` line in its output.
|
||||
|
||||
---
|
||||
|
||||
**3. Why is `curl` the only tool in this module that can confirm the APPLICATION itself is
|
||||
working, not just the network path to it?**
|
||||
|
||||
*Answer:* Address, route, port, and DNS can all be perfectly correct while the application
|
||||
behind them still returns an error or wrong content — `curl` is the only one of these tools that
|
||||
actually completes an HTTP request and inspects the real response.
|
||||
|
||||
---
|
||||
|
||||
**4. `getent hosts` and `dig` both resolve names. When would you reach for `dig` specifically
|
||||
instead of `getent hosts`?**
|
||||
|
||||
*Answer:* When you specifically suspect a DNS server issue and want to query DNS directly with
|
||||
more detail — `getent hosts` checks the same path the rest of the system uses (including local
|
||||
`/etc/hosts` overrides first), which is usually the right first choice, but doesn't show you as
|
||||
much about what the actual DNS server itself said.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Question 3 reinforces Module 3's "layers can each independently succeed or
|
||||
fail" idea with a very concrete example.
|
||||
|
||||
**Approximate timing:** 5 minutes.
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
# Guided Exercise: Which Command Answers This Question?
|
||||
|
||||
This is a paper exercise — commit to an answer before checking it in the Lab.
|
||||
|
||||
## Your task
|
||||
|
||||
For each question, name the ONE command you'd reach for first:
|
||||
|
||||
1. "Does this machine have a working IP address at all?"
|
||||
2. "Is anything listening on port 8080 right now?"
|
||||
3. "Does `payments.internal` resolve to an address?"
|
||||
4. "Is the web server actually returning a valid response, not just accepting connections?"
|
||||
5. "Where does traffic to an address outside my local subnet actually go?"
|
||||
|
||||
## A worked shape to check yourself against
|
||||
|
||||
1. `ip -4 addr show`
|
||||
2. `ss -lnt | grep 8080`
|
||||
3. `getent hosts payments.internal`
|
||||
4. `curl -sf http://.../ -o /dev/null -w '%{http_code}'`
|
||||
5. `ip route`
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Have students justify WHY their chosen command answers the specific question
|
||||
asked, not just that it's "a networking command" — the goal is precision, not familiarity.
|
||||
|
||||
**Approximate timing:** 8-10 minutes live discussion; 5 minutes self-paced.
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
# Reflection: Five Commands, Five Answers, One Diagnosis
|
||||
|
||||
## Reflection questions
|
||||
|
||||
1. Which of these five commands did you already know before this module? Which was
|
||||
genuinely new?
|
||||
2. You now have a real toolkit for Module 3's "where is the failure" question. What's still
|
||||
missing before you'd feel fully confident diagnosing a real production networking issue?
|
||||
|
||||
## Real Ops takeaway
|
||||
|
||||
**Five specific commands, each answering one specific question, beat one vague instinct that
|
||||
"something's wrong with the network."** This is the exact toolkit real engineers reach for
|
||||
first, in this order, when handed an unfamiliar connectivity problem. Module 5 puts all five
|
||||
into one continuous diagnostic story; Module 6 formalizes the discipline around using them.
|
||||
|
||||
This module is complete. Continue to the next module.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Discussion questions for a live classroom:** question 2's honest answer is usually
|
||||
"experience with real, varied failures" — a good moment to set expectations that fluency
|
||||
compounds with practice, not from one module.
|
||||
|
||||
**Approximate timing:** 5-8 minutes live discussion; 5 minutes self-paced writing.
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
# DNS and HTTP
|
||||
|
||||
## The complete request path
|
||||
|
||||
Every time a browser or `curl` fetches a URL, it walks the entire Module 3 chain in order:
|
||||
|
||||
```
|
||||
Application (curl / browser)
|
||||
↓
|
||||
DNS lookup — translate the name to an IP
|
||||
↓
|
||||
IP address obtained
|
||||
↓
|
||||
Routing — figure out the path to that IP
|
||||
↓
|
||||
TCP connection established — a three-way handshake, underneath curl
|
||||
↓
|
||||
HTTP request sent
|
||||
↓
|
||||
Server processes it
|
||||
↓
|
||||
HTTP response returned
|
||||
```
|
||||
|
||||
Nothing here is new — this is Module 3's chain and Module 4's commands, just walked start to
|
||||
finish as ONE continuous story instead of five separate checks.
|
||||
|
||||
## Four distinct failure categories, not one
|
||||
|
||||
Given this path, "it doesn't work" always decomposes into exactly one of four categories:
|
||||
|
||||
1. **DNS failure** — the name never resolved to an IP at all (`curl`'s "Could not resolve
|
||||
host").
|
||||
2. **Routing/connection failure** — the IP was found, but no TCP connection could be established
|
||||
— either refused (something's there but nothing's listening) or timed out (no response at
|
||||
all, usually unreachable or silently blocked).
|
||||
3. **TCP succeeded, but the application never responds correctly** — a connection was
|
||||
established, but the server hangs, drops the connection, or the request never completes.
|
||||
4. **HTTP/application failure** — a full HTTP response comes back, but it's WRONG (a 500 error,
|
||||
a 404, unexpected content) — everything below the application layer worked fine.
|
||||
|
||||
These are genuinely different problems requiring genuinely different fixes — conflating them
|
||||
(treating a 404 the same as a DNS failure) wastes real diagnostic time.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Lesson goal:** Make the four-category failure taxonomy explicit and distinct — this is the
|
||||
direct foundation for this module's diagnostic Lab and for Module 6's formal framework.
|
||||
|
||||
**Approximate timing:** 15 minutes read; 10 minutes live.
|
||||
|
||||
**Common misconception to watch for:** treating an HTTP 500 error the same category as "the
|
||||
network is down" — a 500 means the ENTIRE network path worked; the failure is purely in the
|
||||
application's own logic.
|
||||
|
||||
**Discussion question:** "A request returns HTTP 404. Which of the four categories is that, and
|
||||
what does it rule out?" (Answer: category 4, application/HTTP — it rules out DNS, routing, AND
|
||||
TCP connectivity entirely, since a real HTTP response came back at all.)
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
# Demonstration: Walking the Complete Path, Layer by Layer
|
||||
|
||||
## OBSERVE
|
||||
|
||||
```
|
||||
$ getent hosts lab-internal.test
|
||||
10.99.99.42 lab-internal.test
|
||||
```
|
||||
|
||||
DNS: succeeded.
|
||||
|
||||
## HYPOTHESIS
|
||||
|
||||
Predict: if the next step (TCP connection) fails, what would that specific error look like?
|
||||
|
||||
## INSPECT
|
||||
|
||||
```
|
||||
$ curl http://lab-internal.test:9091/
|
||||
curl: (7) Failed to connect to lab-internal.test port 9091: Connection refused
|
||||
```
|
||||
|
||||
"Connection refused" — DNS already succeeded (ruled out), the failure is specifically that
|
||||
nothing is listening on port 9091 at that address.
|
||||
|
||||
## CHANGE
|
||||
|
||||
Find what IS actually listening, on the correct address:
|
||||
|
||||
```
|
||||
$ ss -lnt | grep LISTEN
|
||||
LISTEN 0 5 0.0.0.0:9095 0.0.0.0:*
|
||||
```
|
||||
|
||||
The real service is on 9095, not 9091 — a configuration/expectation mismatch, not a DNS or
|
||||
routing problem.
|
||||
|
||||
## VERIFY
|
||||
|
||||
```
|
||||
$ curl http://lab-internal.test:9095/
|
||||
app running
|
||||
```
|
||||
|
||||
The full path, once pointed at the CORRECT layer that was actually broken (the port, not DNS),
|
||||
succeeds end to end.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Demo goal:** Show that a real, plausible-sounding complaint ("DNS must be broken") is
|
||||
disproven by evidence in seconds, and the REAL cause is one layer downstream.
|
||||
|
||||
**Approximate timing:** 10 minutes live.
|
||||
|
||||
**Common mistake to watch for:** jumping straight to "restart everything" instead of walking
|
||||
the chain layer by layer to find the SPECIFIC broken step — the whole point of this module's
|
||||
Lab is resisting that shortcut.
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
# Knowledge Check: DNS and HTTP
|
||||
|
||||
---
|
||||
|
||||
**1. Name the four failure categories this module introduced.**
|
||||
|
||||
*Answer:* DNS failure, routing/connection failure, TCP-succeeds-but-application-never-responds,
|
||||
and HTTP/application failure (a full response comes back but it's wrong).
|
||||
|
||||
---
|
||||
|
||||
**2. A request returns HTTP 500. What does that tell you about DNS and TCP connectivity?**
|
||||
|
||||
*Answer:* Both worked completely — a 500 means a full HTTP response was received, so the failure
|
||||
is purely inside the application's own logic, not the network path to reach it.
|
||||
|
||||
---
|
||||
|
||||
**3. Why is "connection refused" a MORE specific diagnosis than "timed out"?**
|
||||
|
||||
*Answer:* "Refused" means a packet reached the destination and was actively rejected (almost
|
||||
always: nothing listening on that port) — you know the destination is reachable. "Timed out"
|
||||
means no response at all, which could mean unreachable, no route, or silently blocked —
|
||||
genuinely less specific.
|
||||
|
||||
---
|
||||
|
||||
**4. A teammate says "DNS must be broken" because a request fails. What should you check
|
||||
BEFORE agreeing?**
|
||||
|
||||
*Answer:* Whether the name actually resolves at all (`getent hosts` or `dig`) — many failures
|
||||
that "feel like DNS" are actually one layer downstream (routing, port, or application), and
|
||||
DNS itself may be working perfectly.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Question 4 mirrors this module's own Lab almost exactly — a good moment to
|
||||
preview it if not already started.
|
||||
|
||||
**Approximate timing:** 5 minutes.
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
# Guided Exercise: Categorize the Failure Before You Fix It
|
||||
|
||||
This is a paper exercise — commit to a category before checking it in the Lab.
|
||||
|
||||
## Your task
|
||||
|
||||
For each symptom, name which of the four failure categories (DNS / routing-connection /
|
||||
TCP-hangs / HTTP-application) it belongs to:
|
||||
|
||||
1. `curl: (6) Could not resolve host: api.internal`
|
||||
2. `curl: (7) Failed to connect to api.internal port 443: Connection refused`
|
||||
3. A request eventually returns, but with HTTP 502 Bad Gateway
|
||||
4. A request just hangs for 30 seconds with no response, then finally times out
|
||||
|
||||
## A worked shape to check yourself against
|
||||
|
||||
1. DNS — the name never resolved.
|
||||
2. Routing/connection — DNS worked, but nothing accepted the connection on that port.
|
||||
3. HTTP/application — a full HTTP response came back (502 is a real, complete response), so
|
||||
DNS/routing/TCP all worked; the failure is in the application layer (often a proxy unable to
|
||||
reach ITS OWN backend, which is itself worth investigating further).
|
||||
4. TCP-hangs / ambiguous-timeout — this is the least specific signature; it could mean
|
||||
unreachable, silently dropped by a firewall, or an application that accepted the connection
|
||||
but never responds — worth narrowing further with `ss`/`ip route` before concluding.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Item 3 (502) is a good one to slow down on — many students initially assume
|
||||
any 5xx code means "the network is broken," when it actually proves the OPPOSITE for every
|
||||
layer below the application.
|
||||
|
||||
**Approximate timing:** 10-12 minutes live discussion; 8 minutes self-paced.
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
# Reflection: You Fixed the Real Problem, Not the Suspected One
|
||||
|
||||
## Reflection questions
|
||||
|
||||
1. Before running any commands in this module's Lab, what would you have guessed was broken?
|
||||
Was your guess right?
|
||||
2. Why does correctly categorizing a failure (DNS vs. routing vs. TCP vs. application) matter
|
||||
more than just "trying things until something works"?
|
||||
|
||||
## Real Ops takeaway
|
||||
|
||||
**"DNS must be broken" is one of the most common wrong first guesses in real incident response
|
||||
— and disproving or confirming it takes seconds, not minutes.** The habit this module builds —
|
||||
categorize the failure precisely before attempting a fix — is what separates a five-minute
|
||||
diagnosis from an hour of trying unrelated things. This exact reasoning, applied to Kubernetes
|
||||
Services/Ingress/DNS later, is one of the most valuable transferable skills in this entire
|
||||
program.
|
||||
|
||||
This module is complete. Continue to the final module of this course.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Discussion questions for a live classroom:** question 1 works well as a live poll before
|
||||
revealing the Lab's actual root cause — the gap between instinct and evidence is the lesson.
|
||||
|
||||
**Approximate timing:** 5-8 minutes live discussion; 5 minutes self-paced writing.
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
# Linux Troubleshooting
|
||||
|
||||
## The RootNode troubleshooting framework
|
||||
|
||||
Every module in ROOT 02 has been building toward this: a repeatable, disciplined process for
|
||||
diagnosing ANY Linux-level failure, not just the specific ones you've already seen.
|
||||
|
||||
```
|
||||
OBSERVE
|
||||
↓
|
||||
GATHER EVIDENCE
|
||||
↓
|
||||
FORM HYPOTHESIS
|
||||
↓
|
||||
TEST
|
||||
↓
|
||||
REPAIR
|
||||
↓
|
||||
VALIDATE
|
||||
↓
|
||||
PREVENT
|
||||
```
|
||||
|
||||
## Each step, and why skipping it costs you later
|
||||
|
||||
- **Observe** — what's the actual, specific symptom? Not "it's broken" — the exact error, the
|
||||
exact command that failed.
|
||||
- **Gather evidence** — logs, process state, listening ports, config content — the raw facts,
|
||||
collected BEFORE guessing at a cause.
|
||||
- **Form hypothesis** — based on the evidence (not a guess made before looking), what's the most
|
||||
likely cause?
|
||||
- **Test** — check whether your hypothesis is actually true, ideally without yet making changes
|
||||
that could mask the real problem.
|
||||
- **Repair** — make the minimal change that addresses the CONFIRMED cause, not a broad "try
|
||||
everything" fix.
|
||||
- **Validate** — confirm the original symptom is actually gone, with real evidence, not
|
||||
assumption.
|
||||
- **Prevent recurrence** — what would stop this from happening again? Skipping this step is how
|
||||
teams end up fixing the exact same incident repeatedly.
|
||||
|
||||
## Why "skip straight to fixing" costs you
|
||||
|
||||
Jumping straight from OBSERVE to REPAIR (skipping evidence and hypothesis) means you're guessing
|
||||
— sometimes you'll get lucky, but you won't have LEARNED anything transferable, and you risk
|
||||
"fixing" a symptom while leaving the actual cause untouched, guaranteeing a repeat. Every command
|
||||
from Modules 1, 2, and 4 IS the "gather evidence" step, made concrete.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Lesson goal:** Consolidate every command this course taught into ONE disciplined process,
|
||||
directly matching the identical framework `root-10-m03-troubleshooting-framework` teaches at the
|
||||
Kubernetes level — introduced here first, at the Linux level, so it already feels familiar by
|
||||
then.
|
||||
|
||||
**Approximate timing:** 15 minutes read; 10 minutes live.
|
||||
|
||||
**Common misconception to watch for:** treating this as a rigid checklist to recite rather than
|
||||
an actual habit — the value only shows up when it's applied under a real, unfamiliar failure
|
||||
(this module's Lab).
|
||||
|
||||
**Discussion question:** "Describe a time you fixed something without understanding WHY it
|
||||
broke. Did it come back?" (Real personal examples land this lesson better than abstract
|
||||
description.)
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
# Demonstration: Every Step, In Order, On a Real Failure
|
||||
|
||||
## OBSERVE
|
||||
|
||||
A startup script fails: `/srv/demo/start.sh` exits with an error. That's the specific, concrete
|
||||
symptom — not "the service is down."
|
||||
|
||||
## GATHER EVIDENCE
|
||||
|
||||
```
|
||||
$ /srv/demo/start.sh
|
||||
FATAL: config file not found: /srv/demo/config/settings.conf
|
||||
```
|
||||
|
||||
The script's own error message IS the evidence — no guessing required.
|
||||
|
||||
## FORM HYPOTHESIS
|
||||
|
||||
The config file at that exact path doesn't exist. That's the specific, testable hypothesis —
|
||||
not "something's misconfigured."
|
||||
|
||||
## TEST
|
||||
|
||||
```
|
||||
$ ls /srv/demo/config/
|
||||
ls: cannot access '/srv/demo/config/': No such file or directory
|
||||
```
|
||||
|
||||
Confirmed — the directory itself doesn't exist yet, not just the file.
|
||||
|
||||
## REPAIR
|
||||
|
||||
```
|
||||
$ mkdir -p /srv/demo/config
|
||||
$ echo "mode=production" > /srv/demo/config/settings.conf
|
||||
```
|
||||
|
||||
The minimal fix for the CONFIRMED cause — not a broader "reinstall everything" overcorrection.
|
||||
|
||||
## VALIDATE
|
||||
|
||||
```
|
||||
$ /srv/demo/start.sh
|
||||
Started with config: mode=production
|
||||
```
|
||||
|
||||
The original symptom is gone, confirmed with real output, not assumption.
|
||||
|
||||
## PREVENT RECURRENCE
|
||||
|
||||
A real prevention step here: the script should probably check for and create its config
|
||||
directory on first run, or the deployment process should ensure the config exists before the
|
||||
service ever starts — naming this explicitly is what separates a real fix from a lucky one.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Demo goal:** Show all seven steps applied in strict order to one concrete, realistic failure
|
||||
— the exact structure this module's Lab requires students to repeat themselves.
|
||||
|
||||
**Approximate timing:** 10 minutes live.
|
||||
|
||||
**Common mistake to watch for:** skipping straight to REPAIR (creating the file) without first
|
||||
articulating the hypothesis and testing it — even when the fix seems obvious, naming the
|
||||
hypothesis explicitly is the habit this module is building.
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
# Knowledge Check: Linux Troubleshooting
|
||||
|
||||
---
|
||||
|
||||
**1. What's the difference between OBSERVE and GATHER EVIDENCE?**
|
||||
|
||||
*Answer:* OBSERVE names the specific symptom (what actually failed, precisely). GATHER EVIDENCE
|
||||
collects the raw facts (logs, config, process/port state) needed to understand WHY, before
|
||||
guessing at a cause.
|
||||
|
||||
---
|
||||
|
||||
**2. Why does FORM HYPOTHESIS come before TEST, rather than jumping straight to REPAIR?**
|
||||
|
||||
*Answer:* A hypothesis is a specific, falsifiable guess at the cause based on evidence already
|
||||
gathered — testing it BEFORE repairing confirms you're about to fix the right thing, rather than
|
||||
making a change that might not address the actual cause at all.
|
||||
|
||||
---
|
||||
|
||||
**3. A fix works, but the same failure happens again a week later. Which framework step was
|
||||
most likely skipped?**
|
||||
|
||||
*Answer:* PREVENT RECURRENCE — the immediate symptom was resolved, but whatever allowed the
|
||||
underlying cause to occur in the first place was never addressed.
|
||||
|
||||
---
|
||||
|
||||
**4. Why is "repair the minimal confirmed cause" better than "try several things until it
|
||||
works"?**
|
||||
|
||||
*Answer:* A minimal, confirmed repair means you actually know what fixed it and can prevent
|
||||
recurrence deliberately. Trying multiple things at once means even success leaves you unable to
|
||||
say WHICH change mattered — and unable to reliably prevent the same failure next time.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Question 3 is a good real-world check-in — ask if anyone has a personal
|
||||
example of a fix that didn't actually stick.
|
||||
|
||||
**Approximate timing:** 5 minutes.
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
# Guided Exercise: Write Your Hypothesis Before You Look at the Answer
|
||||
|
||||
This is a paper exercise — commit to a full framework pass before touching the Lab.
|
||||
|
||||
## Your task
|
||||
|
||||
You're told: "A script that's supposed to write a `ready` marker file isn't creating it, and
|
||||
exits with a non-zero code." Before running anything, write out, specifically:
|
||||
|
||||
1. What's your OBSERVE statement (the precise symptom, not "it's broken")?
|
||||
2. What EVIDENCE would you gather first, and with what command?
|
||||
3. What's ONE specific, testable hypothesis (not "something's wrong with it")?
|
||||
|
||||
## A worked shape to check yourself against
|
||||
|
||||
1. Observe: "Script X exits non-zero and does not create file Y."
|
||||
2. Evidence: redirect both stdout and stderr to a file and read it —
|
||||
`script.sh > log 2>&1; cat log` — the script's own error output is almost always the fastest
|
||||
evidence source for a startup failure.
|
||||
3. Hypothesis (once you've actually read the error): something SPECIFIC the error names —
|
||||
e.g. "the script requires a file at path X that doesn't currently exist" — not a vague
|
||||
restatement of the symptom.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** The value here is entirely in getting students to write a hypothesis
|
||||
SPECIFIC and FALSIFIABLE enough that they could be proven wrong — "something's misconfigured"
|
||||
fails that bar; "the config file at this exact path is missing" passes it.
|
||||
|
||||
**Approximate timing:** 8-10 minutes live discussion; 5 minutes self-paced.
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
# Reflection: This Is the Same Framework You'll Use for the Rest of ROOT_
|
||||
|
||||
## Reflection questions
|
||||
|
||||
1. Which framework step did you personally find easiest to skip or rush through in this
|
||||
module's Lab? Why do you think that step specifically tempts shortcuts?
|
||||
2. You've now used this exact seven-step framework — Observe, Gather Evidence, Form Hypothesis,
|
||||
Test, Repair, Validate, Prevent Recurrence — at the Linux level. Where do you expect to use
|
||||
it again?
|
||||
|
||||
## Real Ops takeaway
|
||||
|
||||
**This entire course, from Module 1's five-question service checklist through Module 5's
|
||||
failure-category taxonomy, was building toward this one repeatable discipline.** You'll meet
|
||||
this exact framework again, formally named, in ROOT 10 — applied to Kubernetes instead of raw
|
||||
Linux. Having already practiced it here means ROOT 10 won't be teaching you a new skill, just
|
||||
retargeting a skill you already have.
|
||||
|
||||
This module — and ROOT 02 — is complete. Continue to ROOT 03: Git & Collaborative Development.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Discussion questions for a live classroom:** question 1 tends to surface "Prevent Recurrence"
|
||||
and "Form Hypothesis" (jumping straight to a guessed fix) as the two most commonly admitted
|
||||
shortcuts — worth normalizing as a real, common temptation rather than a personal failing.
|
||||
|
||||
**Approximate timing:** 8-10 minutes live discussion; 5 minutes self-paced writing.
|
||||
|
||||
**Milestone note:** this is the last module of ROOT 02 — consider a short recap connecting all
|
||||
six modules (services → storage → networking model → inspection tools → DNS/HTTP → framework)
|
||||
before moving to ROOT 03.
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
# Why Version Control Exists
|
||||
|
||||
## The problem: `final.txt`, `final_v2.txt`, `final_v2_ACTUALLY_FINAL.txt`
|
||||
|
||||
Before version control, "keeping history" usually meant copying a file with a new name every
|
||||
time something changed. This fails fast: you lose track of WHAT changed between versions, WHO
|
||||
changed it, and WHY — and it gets completely unworkable the moment more than one person touches
|
||||
the same project.
|
||||
|
||||
## What version control actually gives you
|
||||
|
||||
- **History** — every change, in order, permanently. You can see exactly what a file looked
|
||||
like at any point in the past.
|
||||
- **Reproducibility** — you can recreate the EXACT state of an entire project at any past
|
||||
moment, not just one file.
|
||||
- **Collaboration** — multiple people can work on the same project without emailing files back
|
||||
and forth or overwriting each other's work.
|
||||
- **A source of truth** — one authoritative history everyone agrees is correct, instead of
|
||||
"whoever has the newest-looking filename."
|
||||
- **Rollback** — if a change turns out to be wrong, you can return to a known-good prior state
|
||||
with confidence, not by hoping you kept a backup copy somewhere.
|
||||
|
||||
## Why copying files isn't version control
|
||||
|
||||
Copying `app.py` to `app_backup.py` before making a risky change LOOKS like version control, but
|
||||
it has none of version control's real properties: no record of WHY the change was made, no way
|
||||
to see incremental history, no way to combine two people's simultaneous edits, and no
|
||||
scalability past a handful of files. Git (and version control generally) tracks CHANGES over
|
||||
time as a structured history, not disconnected snapshots you have to manually manage.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Lesson goal:** Motivate Git before teaching a single command — students should feel the real
|
||||
problem (the `final_v2_ACTUALLY_FINAL.txt` pattern) before the solution makes sense.
|
||||
|
||||
**Approximate timing:** 10 minutes read; 8 minutes live.
|
||||
|
||||
**Discussion question:** "Has anyone ever lost work, or overwritten someone else's work, because
|
||||
of exactly this kind of file-copying problem?" (Real personal stories land this lesson far
|
||||
better than abstract description.)
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
# Knowledge Check: Why Version Control Exists
|
||||
|
||||
---
|
||||
|
||||
**1. Name three real properties version control gives you that copying files as backups
|
||||
doesn't.**
|
||||
|
||||
*Answer:* Any three of: structured history of every change, reproducibility of exact past
|
||||
states, real collaboration between multiple people, a single agreed-upon source of truth,
|
||||
confident rollback to a known-good state.
|
||||
|
||||
---
|
||||
|
||||
**2. Why does `final_v2_ACTUALLY_FINAL.txt` fail as a version-control strategy?**
|
||||
|
||||
*Answer:* It has no record of what changed or why between versions, doesn't scale past a
|
||||
handful of files, and provides no way to merge two people's simultaneous changes.
|
||||
|
||||
---
|
||||
|
||||
**3. What does "source of truth" mean in the context of version control?**
|
||||
|
||||
*Answer:* One authoritative, agreed-upon history of a project that everyone treats as
|
||||
correct — as opposed to ambiguity over which copy or filename is the "real" current version.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Keep this brief — it's checking motivation/vocabulary before the hands-on
|
||||
modules begin, not testing depth yet.
|
||||
|
||||
**Approximate timing:** 4 minutes.
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
# Reflection: Naming the Problem You're About to Solve
|
||||
|
||||
## Reflection questions
|
||||
|
||||
1. Have you personally ever lost work, or been confused about which file version was current,
|
||||
in a way version control would have prevented?
|
||||
2. What do you expect to feel DIFFERENT about how you work once you have real history and
|
||||
rollback available?
|
||||
|
||||
## Real Ops takeaway
|
||||
|
||||
**Every engineering practice this program builds toward — code review, CI/CD, GitOps,
|
||||
infrastructure as code — assumes version control as its foundation.** You cannot meaningfully
|
||||
automate deployment, review changes before they ship, or declare infrastructure as code without
|
||||
first having a reliable, structured history of what changed and when. This module named the
|
||||
problem; the rest of ROOT 03 gives you the tool.
|
||||
|
||||
This module is complete. Continue to the next module.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Discussion questions for a live classroom:** question 1 tends to produce genuine, motivating
|
||||
stories — let students share before moving to Module 2's mental model.
|
||||
|
||||
**Approximate timing:** 5 minutes live discussion; 3 minutes self-paced writing.
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
# Git Mental Model
|
||||
|
||||
## Three places your files can be
|
||||
|
||||
Git tracks your project through three distinct areas, and understanding this model is what
|
||||
makes every Git command make sense — memorizing commands without it just produces confusion the
|
||||
first time something doesn't behave as expected.
|
||||
|
||||
```
|
||||
Working Tree → Staging Area → Repository (commit history)
|
||||
(your actual (what you've (permanent,
|
||||
files, as you marked ready recorded
|
||||
edit them) to commit) snapshots)
|
||||
```
|
||||
|
||||
- **Working tree** — the actual files on disk, exactly as you're editing them right now.
|
||||
- **Staging area** (also called "the index") — a holding area for changes you've deliberately
|
||||
marked as ready to be committed. This is Git's most distinctive design choice: you don't
|
||||
commit "everything that changed," you commit "exactly what you staged."
|
||||
- **Repository** — the permanent, recorded history of every commit ever made — this is what
|
||||
`git log` shows you.
|
||||
|
||||
## Why `git add` exists at all
|
||||
|
||||
A change starts in your working tree. `git add <file>` moves it (conceptually — the mechanics
|
||||
are more subtle, but this framing is enough for now) into the staging area. `git commit` takes
|
||||
EVERYTHING currently staged and permanently records it as a new snapshot in the repository's
|
||||
history. This two-step process (stage, then commit) exists so you can build up a commit
|
||||
DELIBERATELY — staging exactly the related changes that belong together, even if your working
|
||||
tree currently has other, unrelated changes you're not ready to commit yet.
|
||||
|
||||
## Core vocabulary
|
||||
|
||||
- **Commit** — one permanently recorded snapshot, with a message describing what changed and
|
||||
why, and a unique identifier (a hash).
|
||||
- **Branch** — a movable pointer to a specific commit, letting you develop something (a
|
||||
"feature branch") without affecting the main line of history until you're ready to combine it
|
||||
back in (Module 4).
|
||||
- **HEAD** — Git's pointer to "which commit you're currently looking at" — almost always the
|
||||
tip of whichever branch you have checked out.
|
||||
- **Remote** — a reference to another copy of the same repository, usually hosted somewhere else
|
||||
(Module 5).
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Lesson goal:** Establish the three-area model BEFORE any commands — same "model before syntax"
|
||||
pedagogy as ROOT 01's process model and ROOT 05's Kubernetes mental model. Every subsequent Git
|
||||
command should be explainable in terms of moving something between these three areas.
|
||||
|
||||
**Approximate timing:** 15 minutes read; 10 minutes live, ideally with a literal diagram drawn
|
||||
live (whiteboard or shared screen) showing files moving between the three areas.
|
||||
|
||||
**Common misconception to watch for:** assuming `git commit` commits every changed file
|
||||
automatically — this is the single most common Git surprise for beginners, and it's exactly
|
||||
what Module 3's Lab will make concrete.
|
||||
|
||||
**Discussion question:** "Why would you ever WANT to stage only some of your changes, not all
|
||||
of them?" (Answer: to build one commit about ONE logical change, even if your working tree
|
||||
currently has several unrelated edits in progress.)
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
# Demonstration: Watching a File Move Between the Three Areas
|
||||
|
||||
## OBSERVE
|
||||
|
||||
```
|
||||
$ echo "draft" > report.txt
|
||||
$ git status --short
|
||||
?? report.txt
|
||||
```
|
||||
|
||||
`??` means: Git sees this file, but it's in neither the staging area nor the repository yet —
|
||||
purely in the working tree.
|
||||
|
||||
## HYPOTHESIS
|
||||
|
||||
Predict what `git status` will show after `git add report.txt`.
|
||||
|
||||
## INSPECT
|
||||
|
||||
```
|
||||
$ git add report.txt
|
||||
$ git status --short
|
||||
A report.txt
|
||||
```
|
||||
|
||||
`A` (in the LEFT column, which represents the staging area) means: staged, ready to commit.
|
||||
|
||||
## CHANGE
|
||||
|
||||
```
|
||||
$ git commit -m "Add report draft"
|
||||
$ git status --short
|
||||
(nothing — clean)
|
||||
```
|
||||
|
||||
## VERIFY
|
||||
|
||||
```
|
||||
$ git log --oneline -1
|
||||
a1b2c3d Add report draft
|
||||
```
|
||||
|
||||
The file traveled through all three areas exactly as the model predicts: working tree (`??`) →
|
||||
staging area (`A`) → repository (a permanent commit in `git log`).
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Demo goal:** Make `git status`'s output legible by directly connecting its symbols (`??`, `A`,
|
||||
clean) to the three-area model just taught, rather than treating them as arbitrary codes to
|
||||
memorize.
|
||||
|
||||
**Approximate timing:** 8 minutes live.
|
||||
|
||||
**Common mistake to watch for:** students reading `git status` output without connecting it
|
||||
back to WHICH of the three areas each symbol refers to — reinforce the mapping explicitly.
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
# Knowledge Check: Git Mental Model
|
||||
|
||||
---
|
||||
|
||||
**1. Name the three areas a file passes through, in order.**
|
||||
|
||||
*Answer:* Working tree → staging area → repository (commit history).
|
||||
|
||||
---
|
||||
|
||||
**2. What does `git add` actually do, in terms of this model?**
|
||||
|
||||
*Answer:* Moves a change from the working tree into the staging area — marking it as ready to
|
||||
be included in the next commit.
|
||||
|
||||
---
|
||||
|
||||
**3. If you edit three files but only `git add` one of them, what happens when you `git
|
||||
commit`?**
|
||||
|
||||
*Answer:* Only the staged file is included in the new commit — the other two edited-but-unstaged
|
||||
files remain as uncommitted changes in the working tree.
|
||||
|
||||
---
|
||||
|
||||
**4. What is HEAD?**
|
||||
|
||||
*Answer:* Git's pointer to the commit you're currently looking at — almost always the tip of
|
||||
whichever branch you have checked out.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Question 3 is the single most important check in this module — if students
|
||||
get this wrong, revisit the diagram before continuing to Module 3.
|
||||
|
||||
**Approximate timing:** 5 minutes.
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
# Guided Exercise: Predict `git status` Before You See It
|
||||
|
||||
This is a paper exercise — commit to a prediction before checking it in the Lab.
|
||||
|
||||
## Your task
|
||||
|
||||
Starting from a clean repository (nothing staged, nothing modified), predict what `git status
|
||||
--short` would show after EACH of these actions, in sequence:
|
||||
|
||||
1. `echo "v1" > file.txt` (a brand new file)
|
||||
2. `git add file.txt`
|
||||
3. `echo "v2" >> file.txt` (modifying the ALREADY-STAGED file further)
|
||||
4. `git commit -m "Add file"`
|
||||
|
||||
## A worked shape to check yourself against
|
||||
|
||||
1. `?? file.txt` — untracked, in the working tree only.
|
||||
2. `A file.txt` — staged (left column).
|
||||
3. `AM file.txt` — this is the surprising one: staged (`A`, left column) AND modified again
|
||||
since staging (`M`, right column) — the staging area has a SNAPSHOT from step 2, and your
|
||||
further edit in step 3 hasn't been staged yet.
|
||||
4. Clean (nothing) — but only the STAGED version (from step 2, without the step-3 edit) actually
|
||||
got committed; the step-3 edit remains as an uncommitted modification afterward.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Item 3 is deliberately the hardest — it's the exact scenario that surprises
|
||||
real beginners ("I thought I already staged this file, why does it show changes again?"). Worth
|
||||
slowing down on.
|
||||
|
||||
**Approximate timing:** 10-12 minutes live discussion; 8 minutes self-paced.
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
# Reflection: The Model Explains the Commands, Not the Other Way Around
|
||||
|
||||
## Reflection questions
|
||||
|
||||
1. Before this module, would `git add` before `git commit` have seemed like an unnecessary
|
||||
extra step? Why does it actually matter now?
|
||||
2. Which of the three areas (working tree, staging, repository) do you feel least confident
|
||||
distinguishing? What would help solidify it?
|
||||
|
||||
## Real Ops takeaway
|
||||
|
||||
**Every Git confusion you'll ever hit traces back to one of these three areas.** "Why isn't my
|
||||
change in the commit," "why does `git status` show something weird," "why did my commit include
|
||||
something I didn't mean to" — all of them are answerable by asking "which of the three areas is
|
||||
this change actually in right now?" You now have that question available; Module 3 makes it
|
||||
muscle memory.
|
||||
|
||||
This module is complete. Continue to the next module.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Discussion questions for a live classroom:** question 2 is worth a quick poll — knowing where
|
||||
the class is least confident tells you where to slow down in Module 3's hands-on Lab.
|
||||
|
||||
**Approximate timing:** 5-8 minutes live discussion; 5 minutes self-paced writing.
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
# Everyday Git
|
||||
|
||||
## The commands you'll actually use constantly
|
||||
|
||||
Module 2 gave you the model. This module gives you the six commands that make up the vast
|
||||
majority of real, everyday Git usage.
|
||||
|
||||
## `git init` — start tracking a project
|
||||
|
||||
```
|
||||
$ git init
|
||||
```
|
||||
|
||||
Creates a new, empty repository in the current directory (a hidden `.git` folder holds
|
||||
everything). You only run this ONCE per project, at the very beginning.
|
||||
|
||||
## `git status` — where do things stand right now?
|
||||
|
||||
Shows what's staged, what's modified-but-unstaged, and what's untracked — the single most
|
||||
useful command for orienting yourself, and one you'll run constantly, almost reflexively,
|
||||
before and after almost every other Git command.
|
||||
|
||||
## `git add` — stage a change
|
||||
|
||||
```
|
||||
$ git add file.txt # stage one specific file
|
||||
$ git add . # stage everything currently changed
|
||||
```
|
||||
|
||||
## `git commit` — permanently record what's staged
|
||||
|
||||
```
|
||||
$ git commit -m "Add configuration loader"
|
||||
```
|
||||
|
||||
A good commit message describes WHAT changed and, ideally, WHY — future-you (or a teammate)
|
||||
reading `git log` months later should understand the change without re-reading the entire diff.
|
||||
|
||||
## `git log` — see the history
|
||||
|
||||
```
|
||||
$ git log --oneline
|
||||
a1b2c3d Add configuration loader
|
||||
9f8e7d6 Initial commit
|
||||
```
|
||||
|
||||
## `git diff` — see exactly what changed, before you commit it
|
||||
|
||||
```
|
||||
$ git diff # unstaged changes vs. the last commit
|
||||
$ git diff --staged # staged changes vs. the last commit
|
||||
```
|
||||
|
||||
Reading your own diff before committing is one of the highest-value habits in this entire
|
||||
course — catching an accidental leftover debug line, or a change you didn't mean to include,
|
||||
BEFORE it becomes a permanent part of history.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Lesson goal:** Convert Module 2's model into the six commands used in nearly every real Git
|
||||
session — this is the fluency floor for everything that follows in ROOT 03.
|
||||
|
||||
**Approximate timing:** 15 minutes read; 10 minutes live.
|
||||
|
||||
**Common misconception to watch for:** treating `git add .` as always safe — it stages
|
||||
EVERYTHING changed, including files you might not have intended to include; `git status` before
|
||||
`git add .` is the habit that prevents this.
|
||||
|
||||
**Demo idea:** run the full init→status→add→commit→log→diff sequence live, narrating `git
|
||||
status`'s changing output at each step.
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
# Demonstration: The Full Cycle, End to End
|
||||
|
||||
## OBSERVE
|
||||
|
||||
```
|
||||
$ git init
|
||||
$ git status --short
|
||||
(nothing — empty repository)
|
||||
```
|
||||
|
||||
## HYPOTHESIS
|
||||
|
||||
Predict what happens to `git status` after creating a new file.
|
||||
|
||||
## INSPECT
|
||||
|
||||
```
|
||||
$ echo "v1" > app.py
|
||||
$ git status --short
|
||||
?? app.py
|
||||
```
|
||||
|
||||
## CHANGE
|
||||
|
||||
```
|
||||
$ git add app.py
|
||||
$ git diff --staged
|
||||
+v1
|
||||
$ git commit -m "Add initial app.py"
|
||||
```
|
||||
|
||||
`git diff --staged` shows exactly what's ABOUT to be committed — a real, final check before it
|
||||
becomes permanent.
|
||||
|
||||
## VERIFY
|
||||
|
||||
```
|
||||
$ git log --oneline
|
||||
3f9a1c2 Add initial app.py
|
||||
$ git status --short
|
||||
(clean)
|
||||
```
|
||||
|
||||
The complete cycle, verified with real evidence at each step — not "I think I committed it,"
|
||||
but confirmed via `git log`.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Demo goal:** Show `git diff --staged` as the final safety check before a commit becomes
|
||||
permanent — a habit worth building explicitly, not just mentioning in passing.
|
||||
|
||||
**Approximate timing:** 8 minutes live.
|
||||
|
||||
**Common mistake to watch for:** skipping `git diff`/`git diff --staged` entirely and committing
|
||||
on faith — a good moment to ask "how would you know if you accidentally staged the wrong
|
||||
thing?"
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
# Knowledge Check: Everyday Git
|
||||
|
||||
---
|
||||
|
||||
**1. What's the difference between `git diff` and `git diff --staged`?**
|
||||
|
||||
*Answer:* `git diff` shows unstaged changes (working tree vs. last commit); `git diff --staged`
|
||||
shows what's currently staged and about to be committed (staging area vs. last commit).
|
||||
|
||||
---
|
||||
|
||||
**2. Why is `git add .` potentially risky compared to `git add <specific-file>`?**
|
||||
|
||||
*Answer:* It stages EVERYTHING currently changed in the directory, which can accidentally
|
||||
include files you didn't mean to commit — checking `git status` first (or using targeted `git
|
||||
add`) avoids this.
|
||||
|
||||
---
|
||||
|
||||
**3. What makes a commit message actually useful months later?**
|
||||
|
||||
*Answer:* Describing WHAT changed and ideally WHY, clearly enough that someone (including
|
||||
future-you) reading `git log` understands the change without re-reading the full diff.
|
||||
|
||||
---
|
||||
|
||||
**4. You run `git init` in a directory that already has an existing Git repository. What
|
||||
happens?**
|
||||
|
||||
*Answer:* `git init` only needs to run once per project — running it again in an already-
|
||||
initialized repository is harmless (it won't destroy existing history) but is never actually
|
||||
necessary.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Question 2 is a good moment to establish "check status before staging broadly"
|
||||
as a house habit for the rest of the course.
|
||||
|
||||
**Approximate timing:** 5 minutes.
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
# Guided Exercise: Plan a Real Commit Before You Make It
|
||||
|
||||
This is a paper exercise — commit to a plan before working in the Lab.
|
||||
|
||||
## Your task
|
||||
|
||||
You've made three unrelated changes to your working tree: fixed a typo in `README.md`, added a
|
||||
new function to `utils.py`, and started (but not finished) an unrelated experiment in
|
||||
`scratch.py` you're not ready to commit. Write out the exact sequence of commands you'd use to
|
||||
end up with exactly ONE clean commit containing the README typo fix, leaving the other two files
|
||||
alone.
|
||||
|
||||
## A worked shape to check yourself against
|
||||
|
||||
```
|
||||
git status
|
||||
git add README.md
|
||||
git diff --staged
|
||||
git commit -m "Fix typo in README"
|
||||
git status
|
||||
```
|
||||
|
||||
The key insight: `git add` lets you choose EXACTLY what goes into the next commit, regardless of
|
||||
what else is sitting, unstaged, in your working tree — `utils.py` and `scratch.py` remain
|
||||
untouched and uncommitted.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** This exercise directly tests whether Module 2's staging-area model actually
|
||||
stuck — if students reach for `git add .`, that's a sign to revisit the model before the Lab.
|
||||
|
||||
**Approximate timing:** 8-10 minutes live discussion; 5 minutes self-paced.
|
||||
@@ -0,0 +1,24 @@
|
||||
# Reflection: You Have Real, Permanent History Now
|
||||
|
||||
## Reflection questions
|
||||
|
||||
1. Look at your own `git log` from this module's Lab. Is your commit message something a
|
||||
stranger (or future-you) could understand without more context?
|
||||
2. Which of the six commands from this module do you expect to type the most, going forward?
|
||||
|
||||
## Real Ops takeaway
|
||||
|
||||
**A real, permanent, inspectable history of your work now exists — this is the actual thing
|
||||
Module 1 named as missing from copy-a-file-as-backup.** Every commit you make from here on is
|
||||
recoverable, comparable, and explainable. Module 4 builds on this by letting multiple lines of
|
||||
work (branches) develop independently before combining — impossible without the reliable
|
||||
history you just built the habit of creating.
|
||||
|
||||
This module is complete. Continue to the next module.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Discussion questions for a live classroom:** question 1 works well as a peer-review pass —
|
||||
have students read a neighbor's commit message and guess what it changed, then compare.
|
||||
|
||||
**Approximate timing:** 5-8 minutes live discussion; 5 minutes self-paced writing.
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
# Branching and Merging
|
||||
|
||||
## Why branches exist
|
||||
|
||||
Without branches, every commit happens on the same single line of history — risky work and
|
||||
finished work are indistinguishable, and two people can't develop different things
|
||||
simultaneously without directly interfering with each other. A **branch** is a movable pointer
|
||||
to a commit, letting you develop something in isolation and only combine it back into the main
|
||||
line when it's actually ready.
|
||||
|
||||
## Creating and switching branches
|
||||
|
||||
```
|
||||
$ git branch feature-login # create a new branch (doesn't switch to it)
|
||||
$ git checkout feature-login # switch to it
|
||||
$ git switch feature-login # the newer, clearer equivalent of checkout for this purpose
|
||||
$ git checkout -b feature-login # create AND switch in one step
|
||||
```
|
||||
|
||||
Commits made while on `feature-login` only affect that branch — `main` is completely unaffected
|
||||
until you deliberately combine them.
|
||||
|
||||
## Merging: combining two branches' history
|
||||
|
||||
```
|
||||
$ git checkout main
|
||||
$ git merge feature-login
|
||||
```
|
||||
|
||||
Git attempts to combine the two branches' history automatically. When the two branches changed
|
||||
DIFFERENT parts of a project, this usually succeeds with no fuss.
|
||||
|
||||
## Divergence and conflicts
|
||||
|
||||
When both branches changed the SAME lines of the SAME file differently, Git can't automatically
|
||||
decide which version is correct — this is a **merge conflict**. Git marks the conflicting
|
||||
section directly in the file:
|
||||
|
||||
```
|
||||
<<<<<<< HEAD
|
||||
your current branch's version
|
||||
=======
|
||||
the incoming branch's version
|
||||
>>>>>>> feature-login
|
||||
```
|
||||
|
||||
A conflict is not an error you did something wrong — it's Git correctly refusing to silently
|
||||
guess which change you actually want. You resolve it by editing the file to the content you
|
||||
actually want (removing the markers), then staging and committing to complete the merge.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Lesson goal:** Frame merge conflicts as an EXPECTED, normal part of collaborative work — not
|
||||
a sign of failure — before students hit their first real one in this module's Lab.
|
||||
|
||||
**Approximate timing:** 15 minutes read; 10 minutes live.
|
||||
|
||||
**Common misconception to watch for:** panicking at the sight of conflict markers — reinforce
|
||||
explicitly that this is Git behaving correctly, not breaking.
|
||||
|
||||
**Demo idea:** deliberately narrate a conflict as "Git is asking YOU to make a decision it
|
||||
can't make safely on its own" rather than "something went wrong."
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
# Demonstration: A Conflict, Caused and Resolved on Purpose
|
||||
|
||||
## OBSERVE
|
||||
|
||||
Two branches both changed line 1 of `config.txt` — `main` to `"env=production"`, `feature` to
|
||||
`"env=staging"`.
|
||||
|
||||
## HYPOTHESIS
|
||||
|
||||
Predict what `git merge feature` will do.
|
||||
|
||||
## INSPECT
|
||||
|
||||
```
|
||||
$ git merge feature
|
||||
Auto-merging config.txt
|
||||
CONFLICT (content): Merge conflict in config.txt
|
||||
Automatic merge failed; fix conflicts and then commit the result.
|
||||
```
|
||||
|
||||
Exactly as predicted — Git refuses to guess.
|
||||
|
||||
## CHANGE
|
||||
|
||||
```
|
||||
$ cat config.txt
|
||||
<<<<<<< HEAD
|
||||
env=production
|
||||
=======
|
||||
env=staging
|
||||
>>>>>>> feature
|
||||
```
|
||||
|
||||
Decide the real answer — here, say `env=production` is correct — and edit the file to exactly
|
||||
that, deleting all three marker lines.
|
||||
|
||||
## VERIFY
|
||||
|
||||
```
|
||||
$ git add config.txt
|
||||
$ git commit -m "Merge feature, keep production config"
|
||||
$ git log --oneline --graph
|
||||
* a1b2c3d Merge feature, keep production config
|
||||
|\
|
||||
| * 9f8e7d6 Update from feature
|
||||
* | 3d2c1b0 Update from main
|
||||
|/
|
||||
* 0a1b2c3 Initial commit
|
||||
```
|
||||
|
||||
The graph shows exactly what happened: two lines of history, deliberately combined.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Demo goal:** Walk a real conflict from cause to resolution without skipping any step,
|
||||
reinforcing that resolution means making an explicit CHOICE, not applying a trick.
|
||||
|
||||
**Approximate timing:** 10 minutes live.
|
||||
|
||||
**Common mistake to watch for:** leaving conflict markers accidentally in the "resolved" file
|
||||
(easy to miss one) — `grep '<<<<<<<' file` before committing catches this reliably.
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
# Knowledge Check: Branching and Merging
|
||||
|
||||
---
|
||||
|
||||
**1. What is a branch, really?**
|
||||
|
||||
*Answer:* A movable pointer to a specific commit — not a copy of the whole project, just a
|
||||
reference letting you develop independently from other branches.
|
||||
|
||||
---
|
||||
|
||||
**2. Why does a merge conflict happen?**
|
||||
|
||||
*Answer:* Both branches changed the SAME lines of the SAME file differently, and Git can't
|
||||
safely guess which version you actually want — so it stops and asks you to decide.
|
||||
|
||||
---
|
||||
|
||||
**3. Is a merge conflict a sign something went wrong?**
|
||||
|
||||
*Answer:* No — it's Git correctly refusing to silently guess at a decision only a human should
|
||||
make. It's a normal, expected part of collaborative work.
|
||||
|
||||
---
|
||||
|
||||
**4. After resolving a conflict by editing the file, what two steps complete the merge?**
|
||||
|
||||
*Answer:* Stage the resolved file (`git add`) and commit (`git commit`) — exactly like
|
||||
completing any other commit, once the conflict markers are gone.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Question 3 is worth stating explicitly and repeating — reframing conflicts
|
||||
as normal, not scary, meaningfully changes how confidently students approach this module's Lab.
|
||||
|
||||
**Approximate timing:** 5 minutes.
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
# Guided Exercise: Decide the Resolution Before You Touch the File
|
||||
|
||||
This is a paper exercise — commit to a decision before resolving anything in the Lab.
|
||||
|
||||
## Your task
|
||||
|
||||
A merge conflict shows:
|
||||
|
||||
```
|
||||
<<<<<<< HEAD
|
||||
timeout_seconds = 30
|
||||
=======
|
||||
timeout_seconds = 120
|
||||
>>>>>>> feature
|
||||
```
|
||||
|
||||
You know `main`'s value (30) was set deliberately for production safety, but `feature`'s value
|
||||
(120) was added while debugging a slow endpoint that genuinely needs more time. Write out: what
|
||||
final content would you put in the file, and why is that the RIGHT resolution (not just "pick
|
||||
one side")?
|
||||
|
||||
## A worked shape to check yourself against
|
||||
|
||||
A well-reasoned resolution here is neither side alone: something like keeping `30` as the
|
||||
DEFAULT but adding a comment or a separate, more specific setting for the slow endpoint
|
||||
specifically — the point is that conflict resolution sometimes means genuinely combining intent
|
||||
from both sides, not mechanically picking `HEAD` or the incoming branch. There's no single
|
||||
"correct" file content — the value is in justifying whichever choice you make with real
|
||||
reasoning, not defaulting to whichever side happens to be `HEAD`.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** The point of this exercise is resisting "just pick a side" as a reflex —
|
||||
some conflicts genuinely require combining or renegotiating intent, not mechanical selection.
|
||||
|
||||
**Approximate timing:** 8-10 minutes live discussion; 5 minutes self-paced.
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
# Reflection: Your First Conflict Wasn't a Disaster
|
||||
|
||||
## Reflection questions
|
||||
|
||||
1. Before this module, how would you have reacted to seeing conflict markers in a real file
|
||||
for the first time?
|
||||
2. What made resolving the conflict in this module's Lab feel manageable (or not)? What would
|
||||
make a REAL conflict, in a large unfamiliar file, harder?
|
||||
|
||||
## Real Ops takeaway
|
||||
|
||||
**Merge conflicts are one of the most feared parts of Git for beginners, and one of the most
|
||||
routine parts of Git for experienced engineers — the difference is just repetition.** You've now
|
||||
caused one on purpose, resolved it deliberately, and verified the result — exactly the
|
||||
experience that turns "conflicts are scary" into "conflicts are Tuesday." Module 5 adds a real
|
||||
remote to this same workflow; conflicts show up there too, and you're now prepared for them.
|
||||
|
||||
This module is complete. Continue to the next module.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Discussion questions for a live classroom:** question 2 is worth extending into "what would
|
||||
make a conflict on a 500-line file, with someone else's unfamiliar code, actually hard" — a good
|
||||
honest preview of real collaborative work.
|
||||
|
||||
**Approximate timing:** 5-8 minutes live discussion; 5 minutes self-paced writing.
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
# Remote Repositories
|
||||
|
||||
## What a remote actually is
|
||||
|
||||
Every repository you've worked with so far has been entirely LOCAL — on this one machine. A
|
||||
**remote** is a reference to another copy of the same repository, typically hosted somewhere
|
||||
else (a server, or in this Lab, a second local repository playing that role). Remotes are what
|
||||
make real collaboration possible: multiple people, multiple machines, all synchronizing through
|
||||
one shared history.
|
||||
|
||||
## `origin`: a name, not a special concept
|
||||
|
||||
By convention, the remote you cloned FROM is named `origin` — but this is just a label Git
|
||||
assigns automatically, not a technical requirement. You could name a remote anything; `origin`
|
||||
is simply the default everyone recognizes.
|
||||
|
||||
## The four remote operations
|
||||
|
||||
- **`git clone <url>`** — copy an ENTIRE remote repository (full history included) to a new
|
||||
local directory, automatically configuring it as `origin`.
|
||||
- **`git fetch`** — download the remote's latest commits WITHOUT changing your own working
|
||||
files or branches — a safe way to see what's new before deciding what to do about it.
|
||||
- **`git pull`** — fetch AND immediately merge the remote's changes into your current branch —
|
||||
effectively `fetch` + `merge` in one step (which is exactly why a `pull` can produce a merge
|
||||
conflict, the same as Module 4's local merges).
|
||||
- **`git push`** — upload your local commits to the remote, making them visible to everyone else
|
||||
using that remote.
|
||||
|
||||
## Upstream: your branch's remote counterpart
|
||||
|
||||
A local branch can be configured to TRACK a specific remote branch (its "upstream") — once set,
|
||||
plain `git pull`/`git push` (no extra arguments) know exactly which remote branch to sync
|
||||
against, rather than requiring you to specify it every time.
|
||||
|
||||
## RootNode uses Gitea (this course does not)
|
||||
|
||||
RootNode's own engineering team uses Gitea (a self-hosted Git service) for its real work — the
|
||||
CONCEPTS in this lesson (origin, clone, fetch, pull, push) are identical regardless of which
|
||||
service hosts a remote (Gitea, GitHub, GitLab, or a private server). This course teaches those
|
||||
concepts using a disposable LOCAL repository standing in for a remote — no account, no network
|
||||
access, and no RootNode credentials are ever needed or exposed in this Lab.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Lesson goal:** Teach remotes as a genuine, transferable concept — students should be able to
|
||||
work with ANY real Git hosting service afterward, not just this Lab's local simulation.
|
||||
|
||||
**Approximate timing:** 15 minutes read; 10 minutes live.
|
||||
|
||||
**Common misconception to watch for:** treating `git pull` as a "safe, no-side-effects" command
|
||||
— it's a merge, and merges can conflict, exactly like Module 4 taught.
|
||||
|
||||
**Discussion question:** "Why might a team prefer `git fetch` followed by manually reviewing
|
||||
changes, over always running `git pull` directly?" (Answer: fetch lets you see what changed
|
||||
before it touches your own working files/branch — useful caution before merging in unfamiliar
|
||||
changes.)
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
# Demonstration: A Complete Clone-to-Push Round Trip
|
||||
|
||||
## OBSERVE
|
||||
|
||||
A bare (remote-style) repository already exists, currently empty.
|
||||
|
||||
```
|
||||
$ git clone /srv/demo-remote.git /srv/demo-clone
|
||||
warning: You appear to have cloned an empty repository.
|
||||
```
|
||||
|
||||
Cloning an empty remote is completely normal — it just means no commits exist there yet.
|
||||
|
||||
## HYPOTHESIS
|
||||
|
||||
Predict what `git remote -v` will show inside the new clone.
|
||||
|
||||
## INSPECT
|
||||
|
||||
```
|
||||
$ cd /srv/demo-clone && git remote -v
|
||||
origin /srv/demo-remote.git (fetch)
|
||||
origin /srv/demo-remote.git (push)
|
||||
```
|
||||
|
||||
Automatically configured as `origin` — exactly as the lesson described, with no extra setup.
|
||||
|
||||
## CHANGE
|
||||
|
||||
```
|
||||
$ echo "# Demo Project" > README.md
|
||||
$ git add README.md
|
||||
$ git commit -m "Add README"
|
||||
$ git push origin main
|
||||
```
|
||||
|
||||
## VERIFY
|
||||
|
||||
```
|
||||
$ git --git-dir=/srv/demo-remote.git log --oneline
|
||||
3f9a1c2 Add README
|
||||
```
|
||||
|
||||
Checking the REMOTE directly (not the clone) proves the push actually reached it — the commit
|
||||
now exists in two places, in sync.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Demo goal:** Prove the push actually worked by inspecting the REMOTE side independently,
|
||||
not just trusting the clone's own `git log`.
|
||||
|
||||
**Approximate timing:** 8 minutes live.
|
||||
|
||||
**Common mistake to watch for:** assuming a successful `git push` with no error message means
|
||||
success without ever double-checking — worth reinforcing the "verify independently" habit from
|
||||
earlier ROOT_ modules.
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
# Knowledge Check: Remote Repositories
|
||||
|
||||
---
|
||||
|
||||
**1. What's the difference between `git fetch` and `git pull`?**
|
||||
|
||||
*Answer:* `git fetch` downloads the remote's new commits without touching your working files or
|
||||
branches. `git pull` does that AND immediately merges them into your current branch — it's
|
||||
fetch + merge combined.
|
||||
|
||||
---
|
||||
|
||||
**2. Why can `git pull` produce a merge conflict?**
|
||||
|
||||
*Answer:* Because it performs a real merge under the hood — if your local branch and the
|
||||
remote's branch both changed the same lines differently, the same conflict situation from
|
||||
Module 4 can occur.
|
||||
|
||||
---
|
||||
|
||||
**3. Is `origin` a required, special name Git enforces?**
|
||||
|
||||
*Answer:* No — it's just the conventional default name Git assigns to the remote you cloned
|
||||
from. You could name a remote something else; `origin` is a widely recognized convention, not a
|
||||
technical requirement.
|
||||
|
||||
---
|
||||
|
||||
**4. Does this course require a GitHub or Gitea account to learn remotes?**
|
||||
|
||||
*Answer:* No — this course uses a disposable local bare repository to safely simulate a real
|
||||
remote, teaching the same concepts (clone/fetch/pull/push/origin) without any account or network
|
||||
access.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Question 2 is worth emphasizing — many students assume `pull` is risk-free
|
||||
until they hit their first real pull-conflict.
|
||||
|
||||
**Approximate timing:** 5 minutes.
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
# Guided Exercise: Fetch First, Decide Second
|
||||
|
||||
This is a paper exercise — commit to a plan before working in the Lab.
|
||||
|
||||
## Your task
|
||||
|
||||
You're about to sync with a shared remote that a teammate has been actively pushing to all day.
|
||||
You're not sure what they changed. Write out the SAFER sequence of commands (as opposed to
|
||||
running `git pull` immediately), and explain what each step lets you verify before your own
|
||||
working files are touched.
|
||||
|
||||
## A worked shape to check yourself against
|
||||
|
||||
```
|
||||
git fetch origin
|
||||
git log HEAD..origin/main --oneline
|
||||
```
|
||||
|
||||
`git fetch` downloads the new commits without touching your branch. The `log` command then
|
||||
shows you exactly what commits exist on the remote that you don't have locally yet — letting you
|
||||
review WHAT changed before deciding to merge it in with a subsequent `git merge origin/main` (or
|
||||
`git pull`, now that you already know roughly what to expect).
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** This exercise directly builds the "fetch first, review, then merge" habit
|
||||
real teams use on shared branches — worth connecting explicitly to why blind `git pull` on a
|
||||
busy shared branch is a common source of surprise conflicts.
|
||||
|
||||
**Approximate timing:** 8-10 minutes live discussion; 5 minutes self-paced.
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
# Reflection: You Just Practiced Real Collaboration, Safely
|
||||
|
||||
## Reflection questions
|
||||
|
||||
1. What would have felt different about this module's Lab if the remote were a real hosted
|
||||
service (Gitea/GitHub) instead of a local simulation? What would have felt the same?
|
||||
2. Where do you expect `push` to matter most in real engineering work you'll do later?
|
||||
|
||||
## Real Ops takeaway
|
||||
|
||||
**Everything you just practiced — clone, commit, push, verified independently on the remote
|
||||
side — is the exact mechanism behind CI/CD and GitOps, which ROOT 11 covers later.** A CI
|
||||
pipeline triggers because someone pushed; a GitOps deployment happens because a change was
|
||||
pushed to a Git repository treated as the source of truth. You haven't just learned Git commands
|
||||
— you've practiced the literal trigger mechanism modern deployment automation is built around.
|
||||
|
||||
This module is complete. Continue to the final module of this course.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Discussion questions for a live classroom:** question 2 previews ROOT 11's GitOps content
|
||||
directly — worth naming that connection explicitly, even without teaching GitOps itself yet.
|
||||
|
||||
**Approximate timing:** 5-8 minutes live discussion; 5 minutes self-paced writing.
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
# Engineering Git Workflow
|
||||
|
||||
## Small commits, meaningful messages
|
||||
|
||||
A commit containing "fixed stuff" and 40 unrelated file changes is nearly useless to anyone
|
||||
reading history later — including future-you. A commit that does ONE logical thing, with a
|
||||
message describing what and why, is what actually makes `git log` a useful tool rather than
|
||||
noise. This isn't pedantry — it's what makes rollback (below) and code review actually
|
||||
practical.
|
||||
|
||||
## `main` as a protected source of truth
|
||||
|
||||
Real engineering teams treat their main branch as the one everyone trusts to be in a working
|
||||
state at all times — direct, unreviewed pushes to `main` are typically restricted specifically
|
||||
BECAUSE it's the shared source of truth every deployment and every other branch is built from.
|
||||
Feature branches (Module 4) exist precisely so risky, in-progress work never threatens that
|
||||
trust.
|
||||
|
||||
## Review: another set of eyes before history is permanent
|
||||
|
||||
Before a change reaches `main`, most real teams have someone else look at it first (a "pull
|
||||
request" or "merge request," depending on the platform). This isn't about distrust — it's a
|
||||
second perspective catching mistakes, and a natural point to discuss WHY a change is being made,
|
||||
not just what it does.
|
||||
|
||||
## History and rollback as safety nets
|
||||
|
||||
Because every commit is permanent and inspectable, a bad change can be identified precisely (via
|
||||
`git log`) and reverted deliberately — confident recovery, not panic, when something goes wrong.
|
||||
This is Module 1's original promise, now fully realized: reproducibility and rollback aren't
|
||||
theoretical, they're what your daily small-commit discipline actually buys you.
|
||||
|
||||
## Why this matters to DevOps, GitOps, and Infrastructure as Code
|
||||
|
||||
Everything in this lesson — small deliberate changes, a trusted source of truth, review before
|
||||
merge, reliable history — is EXACTLY what CI/CD pipelines automate, what GitOps treats a Git
|
||||
repository as authoritative for, and what Infrastructure as Code relies on to safely manage real
|
||||
infrastructure through the same discipline you just practiced on plain text files. ROOT 11
|
||||
builds directly on this course's habits, not a new set of ideas.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Lesson goal:** Close ROOT 03 by explicitly naming the professional discipline (not just Git
|
||||
commands) this course was building, and its direct connection to DevOps/GitOps/IaC.
|
||||
|
||||
**Approximate timing:** 15 minutes read; 10 minutes live.
|
||||
|
||||
**Common misconception to watch for:** treating "review" and "protecting main" as bureaucratic
|
||||
overhead rather than genuine risk management — connect it back to the real cost of a bad change
|
||||
reaching a shared source of truth unreviewed.
|
||||
|
||||
**Discussion question:** "Why might a team enforce small commits and review even when working
|
||||
alone on a personal project?" (Good answers: future-you benefits from the same clarity; building
|
||||
the habit now pays off the moment you DO work with others.)
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
# Demonstration: The Same Change, Told Two Ways
|
||||
|
||||
## OBSERVE
|
||||
|
||||
```
|
||||
$ git log --oneline
|
||||
a1b2c3d fixed stuff
|
||||
```
|
||||
|
||||
One commit, an unhelpful message, and (imagine) 15 unrelated files changed together: a bug fix,
|
||||
a typo correction, and an unrelated half-finished experiment.
|
||||
|
||||
## HYPOTHESIS
|
||||
|
||||
Predict: six months from now, could you (or a teammate) confidently `git revert` just the bug
|
||||
fix from this commit, leaving the other 14 files untouched?
|
||||
|
||||
## INSPECT
|
||||
|
||||
No — reverting this ONE commit reverts everything in it together, including the unrelated
|
||||
typo fix and experiment, whether you want that or not.
|
||||
|
||||
## CHANGE
|
||||
|
||||
The same work, done as three separate, well-described commits instead:
|
||||
|
||||
```
|
||||
$ git log --oneline
|
||||
3f9a1c2 Fix null-pointer crash in payment retry logic
|
||||
9d8e7f6 Fix typo in onboarding email template
|
||||
1a2b3c4 WIP: experiment with new caching strategy (not ready)
|
||||
```
|
||||
|
||||
## VERIFY
|
||||
|
||||
Now `git revert 3f9a1c2` cleanly undoes JUST the payment bug fix, with zero effect on the other
|
||||
two — because each commit is one logical, independently-reversible unit of work.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Demo goal:** Make the practical cost of "fixed stuff" commits concrete — not a style
|
||||
preference, a genuine loss of capability (precise revert, clear history, easier review).
|
||||
|
||||
**Approximate timing:** 8 minutes live.
|
||||
|
||||
**Common mistake to watch for:** treating this as a purely aesthetic preference rather than a
|
||||
practical one — reinforce the concrete "can you revert just the bug fix" test.
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
# Knowledge Check: Engineering Git Workflow
|
||||
|
||||
---
|
||||
|
||||
**1. Why does bundling unrelated changes into one commit cost you something real, not just
|
||||
style points?**
|
||||
|
||||
*Answer:* It makes precise operations like `git revert` impossible to apply to just ONE logical
|
||||
change — reverting the commit reverts everything in it together, whether you want that or not.
|
||||
|
||||
---
|
||||
|
||||
**2. Why do teams typically restrict direct, unreviewed pushes to `main`?**
|
||||
|
||||
*Answer:* `main` is the shared source of truth everything else (deployments, other branches) is
|
||||
built from — protecting it reduces the chance an unreviewed mistake reaches something everyone
|
||||
else depends on being trustworthy.
|
||||
|
||||
---
|
||||
|
||||
**3. Name one concrete way this course's Git discipline connects to GitOps or CI/CD.**
|
||||
|
||||
*Answer:* Any reasonable answer connecting small deliberate changes / a trusted source of truth
|
||||
/ review-before-merge / reliable history to how CI/CD pipelines trigger on pushes or how GitOps
|
||||
treats a Git repository as the authoritative record of desired infrastructure state.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Question 3 is open-ended by design — the goal is students making the
|
||||
connection in their own words, not reciting a fixed answer.
|
||||
|
||||
**Approximate timing:** 5 minutes.
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
# Reflection: You've Practiced Real Engineering Discipline, Not Just Syntax
|
||||
|
||||
## Reflection questions
|
||||
|
||||
1. Look back across all six modules of ROOT 03. Which single habit (small commits, checking
|
||||
`git status` before staging, fetch-before-pull, resolving conflicts deliberately) do you
|
||||
think will matter most in real work?
|
||||
2. This course started with "copying files as backup isn't version control." Can you now
|
||||
explain, in one sentence, what Git actually gives you that copying never could?
|
||||
|
||||
## Real Ops takeaway
|
||||
|
||||
**Version control discipline is not a Git-specific skill — it's an engineering discipline that
|
||||
happens to be exercised through Git.** Small deliberate changes, a trusted shared source of
|
||||
truth, review before permanent history, and confident rollback are the same principles behind
|
||||
CI/CD, GitOps, and Infrastructure as Code, all covered later in ROOT 11. You are not starting
|
||||
that content from zero — you already practiced its foundation here.
|
||||
|
||||
This module — and ROOT 03 — is complete. Continue to ROOT 04: Containers & Docker.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Discussion questions for a live classroom:** question 2 is a good closing check — a clear,
|
||||
confident answer here is real evidence the course's central premise landed.
|
||||
|
||||
**Approximate timing:** 8-10 minutes live discussion; 5 minutes self-paced writing.
|
||||
|
||||
**Milestone note:** this is the last module of ROOT 03 — consider a brief recap connecting all
|
||||
six modules (why version control → the three-area model → everyday commands → branching/
|
||||
conflicts → remotes → professional workflow) before moving to ROOT 04.
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
# Why Containers Exist
|
||||
|
||||
## "It works on my machine"
|
||||
|
||||
Every engineer has heard this sentence, and it names a real, expensive problem: an application
|
||||
that runs perfectly on one machine fails on another because of differences in installed
|
||||
libraries, OS versions, configuration, or dozens of other environmental details that are easy to
|
||||
overlook and hard to fully document.
|
||||
|
||||
## The problem, precisely
|
||||
|
||||
- **Dependency inconsistency** — an app needs Python 3.11 and a specific set of libraries; the
|
||||
production server has Python 3.9 and different library versions.
|
||||
- **Non-repeatability** — setting up a working environment by hand, following a README, drifts
|
||||
over time and between people — "works on my machine" is often true because "my machine" has
|
||||
accumulated years of undocumented manual setup.
|
||||
- **Deployment friction** — moving an application from a developer's laptop to a test server to
|
||||
production means repeating fragile manual setup multiple times, each an opportunity for drift.
|
||||
|
||||
## Containers: package the application AND everything it needs, together
|
||||
|
||||
A **container** packages an application with its exact dependencies, libraries, and
|
||||
configuration into one deployable unit — the same container that ran on a developer's laptop
|
||||
runs, byte-for-byte identically, on a test server or in production. "It works on my machine"
|
||||
becomes "it works in this container," which is true everywhere that container runs.
|
||||
|
||||
## VM vs. container — genuinely different, not just "containers are lighter VMs"
|
||||
|
||||
A **virtual machine** virtualizes an entire computer, including its own full operating system
|
||||
kernel — heavier, slower to start, but with very strong isolation. A **container** shares the
|
||||
HOST machine's kernel and only isolates the application's process, filesystem view, and network
|
||||
— dramatically lighter and faster to start (seconds vs. minutes), with somewhat different
|
||||
isolation properties than a full VM (a well-understood, documented tradeoff, not a hidden flaw).
|
||||
Neither is strictly "better" — they solve overlapping but distinct problems, and real production
|
||||
systems often use both together (VMs providing strong host isolation, containers providing fast,
|
||||
consistent application packaging on top).
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Lesson goal:** Motivate containers through the real, felt problem ("it works on my machine")
|
||||
before any Docker command — mirrors ROOT 03's "why version control exists" opener.
|
||||
|
||||
**Approximate timing:** 12 minutes read; 8 minutes live.
|
||||
|
||||
**Common misconception to watch for:** describing containers as simply "lightweight VMs" —
|
||||
they share the host kernel, which is a genuine architectural difference, not just a size
|
||||
difference.
|
||||
|
||||
**Discussion question:** "Has anyone experienced 'it works on my machine' personally, on either
|
||||
side of that conversation?" (Real stories make this lesson's premise concrete before the
|
||||
technical content begins.)
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
# Demonstration: What "Shares the Host Kernel" Actually Looks Like
|
||||
|
||||
## OBSERVE
|
||||
|
||||
```
|
||||
$ uname -r
|
||||
5.15.0-real-host-kernel
|
||||
$ docker run --rm alpine:3.19 uname -r
|
||||
5.15.0-real-host-kernel
|
||||
```
|
||||
|
||||
## HYPOTHESIS
|
||||
|
||||
Predict: would a real virtual machine show the SAME kernel version as its host, the way this
|
||||
container just did?
|
||||
|
||||
## INSPECT
|
||||
|
||||
A real VM runs its OWN independent kernel — it would very likely show a DIFFERENT version (or
|
||||
even a different OS entirely) than its host, because a VM virtualizes the hardware and boots a
|
||||
complete, separate operating system on top of it.
|
||||
|
||||
## CHANGE
|
||||
|
||||
```
|
||||
$ docker run --rm ubuntu:22.04 uname -r
|
||||
5.15.0-real-host-kernel
|
||||
```
|
||||
|
||||
Even a completely different container OS (`ubuntu` here, vs. `alpine` before) STILL reports the
|
||||
exact same kernel version — because there's only ONE real kernel involved, the host's.
|
||||
|
||||
## VERIFY
|
||||
|
||||
This is the concrete, observable meaning of "containers share the host kernel": no matter what
|
||||
container image you run, `uname -r` always reports the same real kernel underneath — this is
|
||||
exactly why containers start in a fraction of a second (no separate kernel to boot) and exactly
|
||||
the tradeoff the lesson named.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Demo goal:** Make "shares the host kernel" a concrete, checkable fact rather than an abstract
|
||||
architectural claim.
|
||||
|
||||
**Approximate timing:** 8 minutes live.
|
||||
|
||||
**Common mistake to watch for:** assuming different container images run different kernels
|
||||
because their USERSPACE (installed tools, package manager) looks completely different — the
|
||||
kernel underneath is unified regardless.
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
# Knowledge Check: Why Containers Exist
|
||||
|
||||
---
|
||||
|
||||
**1. What real problem does "it works on my machine" name?**
|
||||
|
||||
*Answer:* Dependency/environment inconsistency between where an application was developed and
|
||||
where it actually needs to run — differences in libraries, versions, and configuration that are
|
||||
easy to overlook and hard to fully document by hand.
|
||||
|
||||
---
|
||||
|
||||
**2. What's the actual architectural difference between a container and a virtual machine, not
|
||||
just "containers are smaller"?**
|
||||
|
||||
*Answer:* A container shares the host machine's kernel and only isolates the application's
|
||||
process/filesystem/network view. A VM virtualizes an entire computer, including running its own
|
||||
separate kernel — a genuinely different isolation model, not just a size difference.
|
||||
|
||||
---
|
||||
|
||||
**3. Why do containers start so much faster than VMs?**
|
||||
|
||||
*Answer:* A container doesn't boot a separate operating system/kernel — it starts a process
|
||||
using the ALREADY-RUNNING host kernel, while a VM has to boot an entire independent OS from
|
||||
scratch.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Question 2 is the one worth confirming explicitly — many students carry the
|
||||
misconception "container = tiny VM" past this point without correction.
|
||||
|
||||
**Approximate timing:** 5 minutes.
|
||||
@@ -0,0 +1,26 @@
|
||||
# Reflection: You Now Know What Problem You're About to Solve, Hands-On
|
||||
|
||||
## Reflection questions
|
||||
|
||||
1. Think back to ROOT 01's "how computers run software" module. How does today's lesson refine
|
||||
your understanding of what a "process" actually is?
|
||||
2. Do you expect containers to make the Linux/process/networking skills from ROOT 01-02 less
|
||||
relevant, or MORE relevant? Why?
|
||||
|
||||
## Real Ops takeaway
|
||||
|
||||
**Every skill from ROOT 01-02 — processes, filesystems, networking — transfers directly into
|
||||
containers, because a container is built from exactly those same primitives, just isolated
|
||||
differently.** You are not about to learn an unrelated new subject; you're about to see the
|
||||
Linux fundamentals you already have applied through Docker's specific tooling. Module 2 starts
|
||||
that translation.
|
||||
|
||||
This module is complete. Continue to the next module.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Discussion questions for a live classroom:** question 2's honest answer is "more relevant" —
|
||||
worth stating explicitly, since students sometimes assume containers are an alternative to
|
||||
Linux fundamentals rather than built directly on top of them.
|
||||
|
||||
**Approximate timing:** 5-8 minutes live discussion; 5 minutes self-paced writing.
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
# Images and Containers
|
||||
|
||||
## The single most important distinction in this course
|
||||
|
||||
**An image is a packaged, read-only ARTIFACT. A container is a RUNNING INSTANCE of an image.**
|
||||
This is exactly analogous to a program's executable file (the image) versus a running process
|
||||
of that program (the container) — a distinction ROOT 01's process module already gave you
|
||||
vocabulary for. You can run the SAME image many times, producing many independent containers,
|
||||
the same way you can run the same program multiple times as separate processes.
|
||||
|
||||
## Layers: how an image is actually built
|
||||
|
||||
An image is built from a stack of **layers**, each representing one change (install a package,
|
||||
copy in application code, set a configuration). Layers are cached and shared — if two images
|
||||
both start `FROM alpine:3.19`, they share that base layer on disk rather than duplicating it,
|
||||
which is why pulling a new image that shares layers with one you already have is often much
|
||||
faster than the total image size suggests.
|
||||
|
||||
## Registries, repositories, and tags
|
||||
|
||||
- **Registry** — a server that stores and distributes images (Docker Hub is the default public
|
||||
one; a company might run a private registry).
|
||||
- **Repository** — a named collection of related images (e.g. `nginx`).
|
||||
- **Tag** — a specific version within a repository (`nginx:alpine`, `nginx:1.25`, `nginx:latest`).
|
||||
`latest` is just a conventional tag name, not automatically "the newest" in any enforced sense
|
||||
— relying on it in production is a common, avoidable source of surprise.
|
||||
|
||||
## The core commands, mapped to the model
|
||||
|
||||
- **`docker pull <image>`** — download an image from a registry, without running it.
|
||||
- **`docker run <image>`** — create and start a NEW container from an image (pulls automatically
|
||||
if not already present locally).
|
||||
- **`docker stop <container>`** — gracefully stop a running container (its process, not the
|
||||
image).
|
||||
- **`docker rm <container>`** — delete a stopped container. This does NOT delete the image it
|
||||
was created from.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Lesson goal:** Land the image/container distinction as concretely as possible — nearly every
|
||||
later confusion in this course traces back to conflating the two.
|
||||
|
||||
**Approximate timing:** 15 minutes read; 10 minutes live.
|
||||
|
||||
**Common misconception to watch for:** using "image" and "container" interchangeably in casual
|
||||
speech — gently correct this early and often, since precise vocabulary here prevents real
|
||||
confusion later (e.g., "did you delete the container or the image?" matters).
|
||||
|
||||
**Discussion question:** "If you run `docker run nginx:alpine` three times, how many images
|
||||
exist afterward? How many containers?" (Answer: 1 image, 3 containers — a good check before
|
||||
moving to the hands-on Lab.)
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
# Demonstration: One Image, Many Independent Containers
|
||||
|
||||
## OBSERVE
|
||||
|
||||
```
|
||||
$ docker images | grep alpine
|
||||
alpine 3.19 a1b2c3d4e5f6 2 weeks ago 7.4MB
|
||||
```
|
||||
|
||||
Exactly one `alpine:3.19` image exists locally right now.
|
||||
|
||||
## HYPOTHESIS
|
||||
|
||||
Predict: after running three containers from it, how many images will `docker images` show?
|
||||
|
||||
## INSPECT
|
||||
|
||||
```
|
||||
$ docker run -d --name c1 alpine:3.19 sleep 300
|
||||
$ docker run -d --name c2 alpine:3.19 sleep 300
|
||||
$ docker run -d --name c3 alpine:3.19 sleep 300
|
||||
$ docker images | grep alpine
|
||||
alpine 3.19 a1b2c3d4e5f6 2 weeks ago 7.4MB
|
||||
```
|
||||
|
||||
Still exactly one image — confirmed.
|
||||
|
||||
## CHANGE
|
||||
|
||||
```
|
||||
$ docker ps --format '{{.Names}}: {{.ID}}'
|
||||
c3: 9f8e7d6c5b4a
|
||||
c2: 3d2c1b0a9f8e
|
||||
c1: 7c6b5a4d3e2f
|
||||
```
|
||||
|
||||
Three DIFFERENT containers, each with its own ID, all created from that same one image.
|
||||
|
||||
## VERIFY
|
||||
|
||||
```
|
||||
$ docker rm -f c1 c2 c3
|
||||
$ docker images | grep alpine
|
||||
alpine 3.19 a1b2c3d4e5f6 2 weeks ago 7.4MB
|
||||
```
|
||||
|
||||
Removing all three containers left the image completely untouched — proving containers and the
|
||||
image they came from have independent lifecycles.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Demo goal:** Make the 1-image-many-containers relationship undeniable by actually counting
|
||||
both before and after, rather than just stating it.
|
||||
|
||||
**Approximate timing:** 8 minutes live.
|
||||
|
||||
**Common mistake to watch for:** assuming `docker rm` on a container also removes its image —
|
||||
a good moment to reinforce that images and containers have separate removal commands
|
||||
(`docker rm` for containers, `docker rmi` for images) for exactly this reason.
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
# Knowledge Check: Images and Containers
|
||||
|
||||
---
|
||||
|
||||
**1. State the image/container distinction in one sentence.**
|
||||
|
||||
*Answer:* An image is a packaged, read-only artifact; a container is a running instance created
|
||||
from an image — the same relationship as a program file and a running process of it.
|
||||
|
||||
---
|
||||
|
||||
**2. You run `docker rm -f my-container`. Does this delete the image it was created from?**
|
||||
|
||||
*Answer:* No — removing a container never removes the image; they have independent lifecycles
|
||||
and separate removal commands (`docker rm` vs. `docker rmi`).
|
||||
|
||||
---
|
||||
|
||||
**3. What does the `latest` tag actually guarantee?**
|
||||
|
||||
*Answer:* Nothing enforced — it's just a conventional tag name, not automatically "the newest
|
||||
version" in any technical sense. Relying on it in production is a common source of surprise.
|
||||
|
||||
---
|
||||
|
||||
**4. What's a layer, and why does layer sharing matter practically?**
|
||||
|
||||
*Answer:* A layer is one recorded change in an image's build (installing something, copying
|
||||
files, etc.). Layers are cached and shared between images that share a base — meaning pulling a
|
||||
new image that shares layers with one you already have can be much faster than its total
|
||||
reported size suggests.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Question 2 directly reinforces the previous demo — worth confirming everyone
|
||||
got it right before moving on.
|
||||
|
||||
**Approximate timing:** 5 minutes.
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
# Guided Exercise: Trace the Counts Through a Sequence of Commands
|
||||
|
||||
This is a paper exercise — commit to your answer before checking it in the Lab.
|
||||
|
||||
## Your task
|
||||
|
||||
Starting from a completely clean system (no images, no containers), predict the number of
|
||||
IMAGES and the number of CONTAINERS after each step:
|
||||
|
||||
1. `docker pull redis:alpine`
|
||||
2. `docker run -d --name cache1 redis:alpine`
|
||||
3. `docker run -d --name cache2 redis:alpine`
|
||||
4. `docker rm -f cache1`
|
||||
5. `docker rmi redis:alpine` (attempted while `cache2` is still running)
|
||||
|
||||
## A worked shape to check yourself against
|
||||
|
||||
1. 1 image, 0 containers.
|
||||
2. 1 image, 1 container.
|
||||
3. 1 image, 2 containers.
|
||||
4. 1 image, 1 container (only `cache1` removed).
|
||||
5. This actually FAILS — Docker refuses to remove an image that a running container (`cache2`)
|
||||
still depends on, specifically to prevent exactly this kind of accident. Still 1 image, 1
|
||||
container, plus an error message.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Item 5 is the most valuable one — many students haven't seen Docker's
|
||||
dependency-protection behavior yet, and it's a good real safety feature to highlight.
|
||||
|
||||
**Approximate timing:** 8-10 minutes live discussion; 5 minutes self-paced.
|
||||
@@ -0,0 +1,24 @@
|
||||
# Reflection: A Small Distinction With Large Consequences
|
||||
|
||||
## Reflection questions
|
||||
|
||||
1. Before this module, would you have known why `docker rm` and `docker rmi` are two separate
|
||||
commands? Does it make sense now?
|
||||
2. Where else in ROOT_ have you seen this exact "artifact vs. running instance" pattern before?
|
||||
(Hint: think back to ROOT 01.)
|
||||
|
||||
## Real Ops takeaway
|
||||
|
||||
**Precise vocabulary prevents real mistakes.** Confusing "stop the container" with "delete the
|
||||
image," or assuming `latest` means "the current version," are genuine, common sources of
|
||||
production incidents — not just semantic nitpicking. The image/container distinction you just
|
||||
solidified is the foundation every command in the rest of this course builds on.
|
||||
|
||||
This module is complete. Continue to the next module.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Discussion questions for a live classroom:** question 2's intended answer is "program vs.
|
||||
process," from ROOT 01 — a good moment to explicitly connect the two courses.
|
||||
|
||||
**Approximate timing:** 5-8 minutes live discussion; 5 minutes self-paced writing.
|
||||
@@ -0,0 +1,67 @@
|
||||
# Docker CLI
|
||||
|
||||
## The commands you'll use constantly
|
||||
|
||||
Module 2 introduced pull/run/stop/rm conceptually. This module adds the rest of the everyday
|
||||
toolkit — and every one of them maps directly to a Linux concept you already have from ROOT 01.
|
||||
|
||||
## `docker ps` — what's running right now
|
||||
|
||||
```
|
||||
$ docker ps
|
||||
CONTAINER ID IMAGE STATUS NAMES
|
||||
a1b2c3d4e5f6 nginx:alpine Up 2 minutes web
|
||||
```
|
||||
|
||||
This is the container-level equivalent of ROOT 02's `ps aux` — "what's actually running." Add
|
||||
`-a` to see stopped containers too, not just running ones.
|
||||
|
||||
## `docker logs` — what has it been saying?
|
||||
|
||||
```
|
||||
$ docker logs web
|
||||
```
|
||||
|
||||
The container-level equivalent of reading a service's log file from ROOT 02 — except Docker
|
||||
captures a container's stdout/stderr automatically, so there's no config needed to find WHERE
|
||||
the logs live, unlike a traditional service.
|
||||
|
||||
## `docker exec` — run a NEW command inside an ALREADY-running container
|
||||
|
||||
```
|
||||
$ docker exec web sh -c 'cat /etc/nginx/nginx.conf'
|
||||
```
|
||||
|
||||
Different from `docker run`, which creates a brand-new container — `exec` reaches INTO an
|
||||
existing, running one, exactly like ROOT 01's `ps`/inspection commands but scoped to one
|
||||
specific container's process namespace.
|
||||
|
||||
## `docker inspect` — the full, detailed truth about a container
|
||||
|
||||
```
|
||||
$ docker inspect web --format '{{.State.Running}}'
|
||||
true
|
||||
```
|
||||
|
||||
Returns a large JSON document with everything Docker knows about a container — `--format` lets
|
||||
you extract just the one field you care about, rather than reading the whole thing.
|
||||
|
||||
## `docker stop` vs `docker rm`
|
||||
|
||||
`docker stop` gracefully stops a running container (sends SIGTERM, then SIGKILL after a grace
|
||||
period — literally ROOT 01's process-signals lesson, applied to a container's main process).
|
||||
`docker rm` deletes a STOPPED container entirely — `docker rm -f` combines both (force-stop then
|
||||
remove) in one step.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Lesson goal:** Explicitly map every Docker CLI command back to the equivalent ROOT 01/02
|
||||
Linux concept students already have — this is a translation exercise, not new material.
|
||||
|
||||
**Approximate timing:** 15 minutes read; 10 minutes live.
|
||||
|
||||
**Common misconception to watch for:** confusing `docker run` (creates a new container) with
|
||||
`docker exec` (runs a command in an existing one) — a very common early mixup.
|
||||
|
||||
**Demo idea:** run `docker exec` against a container, then immediately `docker run` the same
|
||||
image again, and compare `docker ps` before/after to show the difference concretely.
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
# Demonstration: `run` Creates, `exec` Reaches In
|
||||
|
||||
## OBSERVE
|
||||
|
||||
```
|
||||
$ docker run -d --name web nginx:alpine
|
||||
$ docker ps --format '{{.Names}}'
|
||||
web
|
||||
```
|
||||
|
||||
One container exists.
|
||||
|
||||
## HYPOTHESIS
|
||||
|
||||
Predict: will `docker exec web echo hello` create a SECOND container?
|
||||
|
||||
## INSPECT
|
||||
|
||||
```
|
||||
$ docker exec web echo hello
|
||||
hello
|
||||
$ docker ps --format '{{.Names}}'
|
||||
web
|
||||
```
|
||||
|
||||
Still exactly one container — `exec` ran a new command INSIDE the existing one, it never
|
||||
created anything new.
|
||||
|
||||
## CHANGE
|
||||
|
||||
Now actually run the same image again:
|
||||
|
||||
```
|
||||
$ docker run -d --name web2 nginx:alpine
|
||||
$ docker ps --format '{{.Names}}'
|
||||
web2
|
||||
web
|
||||
```
|
||||
|
||||
Now two containers exist — because `run`, unlike `exec`, always creates a NEW container.
|
||||
|
||||
## VERIFY
|
||||
|
||||
```
|
||||
$ docker logs web | tail -3
|
||||
$ docker inspect web --format '{{.State.Running}}'
|
||||
true
|
||||
```
|
||||
|
||||
Both containers are independent, each with their own logs and state — exactly as Module 2's
|
||||
"one image, many containers" demonstration showed.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Demo goal:** Directly contrast `run` and `exec`'s effect on the container COUNT — this is the
|
||||
most common early confusion and deserves a side-by-side comparison.
|
||||
|
||||
**Approximate timing:** 8 minutes live.
|
||||
|
||||
**Common mistake to watch for:** trying to `exec` into a container that isn't running yet
|
||||
(`exec` requires an ALREADY-running target) — a good moment to connect back to `docker ps`
|
||||
first.
|
||||
@@ -0,0 +1,39 @@
|
||||
# Knowledge Check: Docker CLI
|
||||
|
||||
---
|
||||
|
||||
**1. What's the real difference between `docker run` and `docker exec`?**
|
||||
|
||||
*Answer:* `docker run` always creates a brand-new container from an image. `docker exec` runs a
|
||||
new command inside an ALREADY-running container — no new container is created.
|
||||
|
||||
---
|
||||
|
||||
**2. Which command is the container-level equivalent of ROOT 02's "what's running" question
|
||||
(`ps aux` / service inspection)?**
|
||||
|
||||
*Answer:* `docker ps` (add `-a` to include stopped containers too).
|
||||
|
||||
---
|
||||
|
||||
**3. Why doesn't `docker logs` require you to know a specific log file path, unlike a
|
||||
traditional Linux service?**
|
||||
|
||||
*Answer:* Docker automatically captures a container's stdout/stderr output — there's no
|
||||
separate log file configuration needed the way ROOT 02's services required.
|
||||
|
||||
---
|
||||
|
||||
**4. What does `docker stop` actually send to a container's main process, and why does that
|
||||
matter?**
|
||||
|
||||
*Answer:* SIGTERM first (a polite shutdown request, giving the process a chance to clean up),
|
||||
then SIGKILL after a grace period if it hasn't exited — the exact same signal discipline ROOT 01
|
||||
taught for regular Linux processes, applied here to a container's main process.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Question 1 is the single most important check in this module — revisit the
|
||||
demo if students get it wrong.
|
||||
|
||||
**Approximate timing:** 5 minutes.
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
# Guided Exercise: Which Docker Command Actually Does This?
|
||||
|
||||
This is a paper exercise — commit to an answer before checking it in the Lab.
|
||||
|
||||
## Your task
|
||||
|
||||
For each goal, name the ONE Docker command you'd use:
|
||||
|
||||
1. "See every container, including ones that have stopped."
|
||||
2. "Run a one-off diagnostic command inside a container that's already running."
|
||||
3. "Read what a container has printed to its logs so far."
|
||||
4. "Check the exact value of one specific field (like whether it's currently running) without
|
||||
reading the entire inspect output."
|
||||
5. "Completely remove a running container in one step, without stopping it first separately."
|
||||
|
||||
## A worked shape to check yourself against
|
||||
|
||||
1. `docker ps -a`
|
||||
2. `docker exec <container> <command>`
|
||||
3. `docker logs <container>`
|
||||
4. `docker inspect <container> --format '{{...}}'`
|
||||
5. `docker rm -f <container>`
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Have students justify why each answer fits the described GOAL specifically,
|
||||
not just recite familiar syntax.
|
||||
|
||||
**Approximate timing:** 8-10 minutes live discussion; 5 minutes self-paced.
|
||||
@@ -0,0 +1,24 @@
|
||||
# Reflection: The Whole Toolkit Was Already Familiar
|
||||
|
||||
## Reflection questions
|
||||
|
||||
1. Which Docker command in this module felt like "a new version of something I already knew"
|
||||
from ROOT 01 or ROOT 02? Which felt genuinely new?
|
||||
2. You ran, inspected, execed into, and removed a real container this module. What's the ONE
|
||||
command you'd reach for first if a container "seemed broken" and you didn't know why yet?
|
||||
|
||||
## Real Ops takeaway
|
||||
|
||||
**Every Docker command in this module is a direct translation of a Linux skill you already
|
||||
had.** `docker ps` is `ps aux`. `docker logs` is reading a log file, minus the config. `docker
|
||||
exec` is reaching into a running process. This is exactly why ROOT 01-02 came before ROOT 04 —
|
||||
containers don't replace Linux fundamentals, they wrap them in a specific, consistent interface.
|
||||
|
||||
This module is complete. Continue to the next module.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Discussion questions for a live classroom:** question 2's honest answer is usually `docker ps`
|
||||
or `docker logs` — a good moment to connect back to ROOT 02's five-question service checklist.
|
||||
|
||||
**Approximate timing:** 5-8 minutes live discussion; 5 minutes self-paced writing.
|
||||
+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.
|
||||
+61
@@ -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.)
|
||||
+63
@@ -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.
|
||||
+39
@@ -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.
|
||||
+29
@@ -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.
|
||||
+50
@@ -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.
|
||||
+62
@@ -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.
|
||||
+38
@@ -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.
|
||||
+35
@@ -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.
|
||||
+24
@@ -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.
|
||||
+65
@@ -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.)
|
||||
+64
@@ -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.
|
||||
+36
@@ -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.
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
# Containers → Kubernetes
|
||||
|
||||
## What you've actually built, across this entire course
|
||||
|
||||
You can now package an application (Dockerfile), run it in isolation (containers), connect
|
||||
multiple containers together (networking, Compose), and persist their data reliably (volumes).
|
||||
That's a complete, real skill set for running an application on ONE machine. Kubernetes exists
|
||||
to answer the question this course deliberately never had to ask: **what happens when "one
|
||||
machine" isn't enough?**
|
||||
|
||||
## What Docker already solves
|
||||
|
||||
- **Application packaging** — a Dockerfile makes an environment reproducible anywhere.
|
||||
- **Process isolation** — a container is isolated from other processes on the same machine.
|
||||
- **Basic networking** — containers can reach each other and be reached from outside, on one
|
||||
host.
|
||||
- **Basic multi-container orchestration** — Compose runs a small set of related containers
|
||||
together, on one host.
|
||||
|
||||
## What Kubernetes adds on top
|
||||
|
||||
- **Scheduling** — deciding WHICH machine, out of potentially hundreds, a container should run
|
||||
on — a question that doesn't exist when you only have one machine.
|
||||
- **Desired state and reconciliation** — you declare what SHOULD be running; Kubernetes
|
||||
continuously works to make reality match that declaration, even after a machine fails.
|
||||
- **Controllers** — automated processes that watch and correct drift from desired state (the
|
||||
generalized version of "restart it if it crashes," running constantly, across the whole
|
||||
cluster).
|
||||
- **Service discovery at scale** — reaching "the right instance of a service" when there might
|
||||
be dozens of them, spread across many machines, changing constantly.
|
||||
- **Health management** — automatically detecting an unhealthy container and replacing it,
|
||||
without a human first noticing something's wrong.
|
||||
- **Scaling** — running more or fewer copies of something based on real demand, automatically.
|
||||
- **Rollout strategies** — deploying a new version across many running instances safely, with a
|
||||
defined strategy for how to do it without an outage.
|
||||
- **Distributed orchestration** — coordinating ALL of the above across a fleet of machines as if
|
||||
it were one system.
|
||||
|
||||
## The honest bridge
|
||||
|
||||
None of this makes Docker/containers obsolete — Kubernetes runs containers, using the exact
|
||||
container runtime concepts this course just taught. Kubernetes is the layer ABOVE containers
|
||||
that exists specifically because real production systems eventually need more than one machine,
|
||||
more reliability than manual restarts, and more automation than a human watching dashboards. You
|
||||
are not about to learn a replacement for what you just built — you're about to learn what
|
||||
happens when you need to run it at a scale one machine can't handle.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Lesson goal:** Deliver the explicit "why Kubernetes" answer this entire foundational half of
|
||||
ROOT_ has been building toward — students should be able to state, in their own words, what
|
||||
specifically Kubernetes adds that Docker alone doesn't.
|
||||
|
||||
**Approximate timing:** 15 minutes read; 10 minutes live.
|
||||
|
||||
**Common misconception to watch for:** treating Kubernetes as "a replacement for Docker" rather
|
||||
than "an orchestration layer that runs containers across many machines" — the container
|
||||
concepts from this entire course remain fully applicable inside Kubernetes.
|
||||
|
||||
**Discussion question:** "Name one thing from this list (scheduling, desired state, controllers,
|
||||
service discovery, health management, scaling, rollout, distributed orchestration) that would be
|
||||
genuinely hard to do by hand with just `docker run` commands across many machines." (Almost any
|
||||
honest answer works — the point is feeling the real gap Kubernetes fills, not memorizing the
|
||||
list.)
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
# Knowledge Check: Containers → Kubernetes
|
||||
|
||||
---
|
||||
|
||||
**1. Does Kubernetes replace Docker/containers?**
|
||||
|
||||
*Answer:* No — Kubernetes orchestrates containers across many machines; it runs on top of the
|
||||
same container concepts this course taught, not instead of them.
|
||||
|
||||
---
|
||||
|
||||
**2. Name one thing Kubernetes adds that a single-machine `docker run`/Compose setup
|
||||
fundamentally can't do.**
|
||||
|
||||
*Answer:* Any reasonable answer from: scheduling across multiple machines, automatic
|
||||
reconciliation to a desired state after a failure, service discovery across many changing
|
||||
instances, automated health-based replacement, demand-based scaling, or coordinated rollout
|
||||
across a fleet.
|
||||
|
||||
---
|
||||
|
||||
**3. What does "desired state" mean, in the sense this lesson introduced it?**
|
||||
|
||||
*Answer:* You declare what SHOULD be running; the orchestrator continuously works to make actual
|
||||
reality match that declaration, including automatically after a failure — rather than a person
|
||||
having to manually notice and fix drift.
|
||||
|
||||
---
|
||||
|
||||
**4. Why doesn't this course teach Kubernetes directly, ending here instead?**
|
||||
|
||||
*Answer:* This course's job was building the container/Linux/Git foundation Kubernetes assumes
|
||||
— ROOT 05 builds on exactly this foundation to teach Kubernetes itself.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Question 2 is worth collecting several different answers from the group — the
|
||||
variety itself is evidence the "why Kubernetes" question actually landed.
|
||||
|
||||
**Approximate timing:** 5 minutes.
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
# Reflection: You're Ready, By Design, Not By Accident
|
||||
|
||||
## Reflection questions
|
||||
|
||||
1. Look back across ROOT 01 (Linux/processes), ROOT 02 (networking/troubleshooting), ROOT 03
|
||||
(Git), and ROOT 04 (containers). Which single skill do you expect to use MOST once you start
|
||||
ROOT 05?
|
||||
2. Can you explain, in your own words and without looking anything up, why Kubernetes exists?
|
||||
If you can't yet, which part of this module's lesson would you reread?
|
||||
|
||||
## Real Ops takeaway
|
||||
|
||||
**Every module across all four foundational courses was chosen deliberately to make THIS moment
|
||||
make sense.** Processes (ROOT 01) become container processes become Pods. Filesystem/storage
|
||||
(ROOT 01-02) becomes container volumes becomes Kubernetes storage. Networking/DNS/troubleshooting
|
||||
(ROOT 02) becomes container networking becomes Services/Ingress/NetworkPolicy. Git (ROOT 03)
|
||||
becomes the foundation for GitOps later. None of ROOT 05's content will be starting from zero —
|
||||
you are walking in already equipped, on purpose.
|
||||
|
||||
This module — and the entire ROOT_ Foundations sequence (ROOT 01-04) — is complete. Continue to
|
||||
ROOT 05: Kubernetes Foundations.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Discussion questions for a live classroom:** question 2 is the single best readiness check for
|
||||
this entire foundational sequence — a confident, correct answer here is real evidence students
|
||||
are ready for ROOT 05, not just that they clicked through four courses.
|
||||
|
||||
**Approximate timing:** 8-10 minutes live discussion; 5 minutes self-paced writing.
|
||||
|
||||
**Milestone note:** this is the last module of the entire ROOT_ Foundations sequence (ROOT 01-04)
|
||||
— consider a genuine celebratory moment here for a live cohort; this represents a real, complete
|
||||
skill foundation, not just a checkpoint.
|
||||
@@ -79,7 +79,11 @@ decided, and it never invents new state for anything that's independently verifi
|
||||
from (a lesson has no "pass/fail," a reflection has no validator). `POST /api/student/
|
||||
activities/:id/complete` refuses (409 `not_manually_completable`) for any activity whose
|
||||
`completion_behavior` isn't `manual_marker` — a student can never fake a lab or assessment
|
||||
completion through this route.
|
||||
completion through this route. **(Phase 6E)** the route also resolves the activity's own
|
||||
course/module through the same `buildCourseTree()` lock computation every other route trusts,
|
||||
and refuses (403 `locked`) if either is locked for that student — a real, pre-existing gap
|
||||
found and fixed while proving the ROOT 01-04 prerequisite chain; see
|
||||
`docs/PHASE6E_ROOT_FOUNDATIONS_COMPLETION.md`.
|
||||
|
||||
Course and module completion are pure aggregates over their activities' completion (`total`/
|
||||
`completed`/`pct`/`complete` — see `totals()`), never separately stored.
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
# Phase 6E — ROOT Foundations Completion
|
||||
|
||||
Phase 6E's mission: finish what Phase 6D deliberately left for a future phase — ROOT 02 (Linux
|
||||
Administration & Networking), ROOT 03 (Git & Collaborative Development), and ROOT 04
|
||||
(Containers & Docker) — completing the real ROOT 01→02→03→04→05 foundational path.
|
||||
|
||||
## Baseline verification
|
||||
|
||||
Confirmed at start: branch `main`, clean tree, HEAD = `origin/main` = Phase 6D's
|
||||
`b6516a74db2d5a5d53d2f1f76c5f2d74e4ed35eb`.
|
||||
|
||||
## ROOT 02, 03, 04 — architecture, content, labs
|
||||
|
||||
All three courses were authored to the exact same gold-module standard already proven in ROOT
|
||||
01: real lessons/demonstrations/knowledge-checks/guided-exercises/reflections, real hands-on labs
|
||||
using the existing generic scenario/validation engine, and a wired course assessment. See
|
||||
`docs/ROOT_FOUNDATIONS_PILOT_READINESS.md` for the full module-by-module evidence and
|
||||
`docs/ROOT_PILOT_CURRICULUM.md` for the pilot manifest detail.
|
||||
|
||||
**ROOT 02** (6 modules, 37 activities): the Networking Mental Model module (M3) received
|
||||
deliberately extra depth per its "critical module" designation — no lab by design (conceptual
|
||||
mastery via knowledge check and guided exercise), with every later module building on it
|
||||
explicitly. Real Labs: a nginx-inspection task that never touches the platform's own reverse
|
||||
proxy, a controllable start/stop service lifecycle, disk-usage forensics, `ip`/`ss`/`getent`
|
||||
diagnostics, and a genuine DNS-resolves-but-port-is-wrong diagnose-and-fix.
|
||||
|
||||
**ROOT 03** (6 modules, 31 activities): uses only real LOCAL Git repositories — a real merge
|
||||
conflict caused on purpose and resolved by the student, and a real local bare repository
|
||||
standing in for a remote (clone/commit/push, verified independently on the "remote" side). No
|
||||
Gitea account, no network access, no RootNode credential of any kind ever reaches a student Lab.
|
||||
|
||||
**ROOT 04** (8 modules, 43 activities): uses the nested Docker Engine Phase 6D built and tested.
|
||||
Real Labs: `docker run`/`exec`/`inspect` fluency, a genuine "works internally, unreachable from
|
||||
host" port-publishing diagnosis and fix, a real destroy/recreate volume-persistence proof, a
|
||||
real Dockerfile build, and a real two-service Compose application.
|
||||
|
||||
## The central architectural finding, confirmed again
|
||||
|
||||
Every new scenario this phase (Linux, Git, Docker) uses the exact same generic scenario/
|
||||
validation engine ROOT 01 proved — `runCommand()` executes an arbitrary shell command and checks
|
||||
its output, nothing Kubernetes-specific. No new activity type, no new mechanism.
|
||||
|
||||
## A real, reproducible dash-vs-bash bug, found and fixed
|
||||
|
||||
`&>file` (bash's combined-redirect shorthand) silently misparses under `/bin/sh` (dash, this
|
||||
image's actual shell, confirmed via `readlink -f /bin/sh`), which is what
|
||||
`backend/server.js`'s `runCommand()` uses — a scenario's `setup_commands` using `&>` captured
|
||||
the WRONG process's PID via `$!`, breaking a later `kill $(cat pid)` step with "No such
|
||||
process." Found while testing `linux-service-lifecycle` and `linux-dns-http-diagnosis` against
|
||||
the real API (not assumed). Fixed by using the POSIX-portable `>file 2>&1` form in both affected
|
||||
scenarios' `setup_commands`/hints.
|
||||
|
||||
## A real, pre-existing security gap, found and fixed
|
||||
|
||||
Testing the real ROOT 01→02→03→04→05 chain via direct API (Step 11's explicit requirement)
|
||||
revealed that `POST /api/student/activities/:activityId/complete`
|
||||
(`platform/api/src/course-routes.js`) checked only that the activity exists and the student
|
||||
holds an active PROGRAM enrollment — **never** whether the activity's own course/module was
|
||||
actually unlocked for that student. A direct POST could mark any `manual_marker` activity
|
||||
complete regardless of the prerequisite chain. This is the exact class of gap Phase 6B already
|
||||
fixed once for the capstone's own start route, just never closed for this more general route.
|
||||
|
||||
**Proven to predate Phase 6E**, not introduced by it: reproduced against an unrelated ROOT 05/06
|
||||
pair before touching any Phase 6E content, confirming the gap was real and pre-existing, just
|
||||
never triggered before because ROOT 01-04 (and therefore any REAL prerequisite gate before ROOT
|
||||
05) didn't exist with real content until this phase and Phase 6D.
|
||||
|
||||
**Fix**: the route now calls the same `buildCourseTree()`/lock computation every other route
|
||||
already trusts, resolves the activity's own course and module within that tree, and returns
|
||||
`403 { error: 'locked' }` if either is locked — reusing the exact mechanism, not a second,
|
||||
duplicated check. Verified live: both a ROOT 02 bypass attempt (behind the new chain) and a
|
||||
ROOT 06 bypass attempt (a completely unrelated, pre-existing course pair) are now correctly
|
||||
rejected, while legitimate unlocked completions still succeed.
|
||||
|
||||
## Regression fallout from a genuine, correct fix — and what it revealed
|
||||
|
||||
Enforcing this lock for real broke several EARLIER e2e suites' own historical shortcuts: tests
|
||||
written under the old (buggy) permissive behavior had accumulated small ordering gaps —
|
||||
completing a course's modules out of the order its own real prerequisite chain requires, or
|
||||
skipping a scenario the ALL_STAGES/FOUNDATION lists never included because the corresponding
|
||||
activity was added in a later phase. None of these were platform bugs; each was a stale test
|
||||
assumption the missing enforcement had been silently covering for. Found and fixed, with the
|
||||
underlying scenario gap named explicitly in each case:
|
||||
|
||||
- `e2e-phase6a.js`/`e2e-phase6b.js`/`e2e-phase6c.js`/`e2e-phase6d.js`: needed the real ROOT
|
||||
01-04 completion sequence injected before touching ROOT 05+ content, since ROOT 05's real
|
||||
prerequisite (ROOT 04) is no longer vacuously satisfied. Extracted into a new shared
|
||||
`platform/test/foundations-helper.js` (`completeRootFoundations01Through04`,
|
||||
`completeThroughRoot06`, `completeThroughRoot10`) rather than duplicating ~30-80 lines four
|
||||
times.
|
||||
- `e2e-phase6b.js`'s `bypass` student flow: needed `deployment-rollback`, `job-task`,
|
||||
`cronjob-task`, and `container-logging-mcq` scenarios completed explicitly — all four exist in
|
||||
the real curriculum but were added in phases after the original `ALL_STAGES` list was written,
|
||||
so completing "every stage scenario" via that stale list never actually finished
|
||||
`root-06`/`root-10` the COURSE, only their originally-scoped subset.
|
||||
- `e2e-phase6a.js`'s and `e2e-phase6d.js`'s own ROOT 01/05 completion tests needed each module
|
||||
completed fully (lesson→demo→kc→guided→**lab→reflection**) before moving to the next, since
|
||||
ROOT 01's (and ROOT 05's) own modules carry real intra-course prerequisites — completing every
|
||||
module's front half first and circling back for labs/reflections later is exactly the ordering
|
||||
the newly-enforced lock now correctly rejects.
|
||||
- Two now-obsolete Phase-6D-era assertions in `e2e-phase6d.js` ("ROOT 02-04 stay PLANNED," "ROOT
|
||||
03-05 stay vacuously unlocked") were updated to assert the opposite, with a comment explaining
|
||||
Phase 6E intentionally changed that fact.
|
||||
|
||||
## Testing
|
||||
|
||||
`platform/test/e2e-phase6e.js` — 23/23 passing (fresh stack): full ROOT 02/03/04 hierarchy,
|
||||
content, and assessment-pool verification; the complete ROOT 01→02→03→04→05 chain proven on one
|
||||
student (each course's completion unlocking exactly the next, nothing further); the direct-API
|
||||
lock bypass proven blocked on both a Phase 6E course pair and a pre-existing, unrelated pair;
|
||||
existing ROOT 06+/Capstone/`is_required` behavior confirmed unaffected; self-paced no-instructor-
|
||||
deferral scan; instructor-notes exclusion and RBAC; logout/login persistence; cross-student
|
||||
isolation; content-version stability; content validation.
|
||||
|
||||
## Real-Lab rehearsal
|
||||
|
||||
`infra/scripts/drills/test-phase6e-foundations-rehearsal.js` — 13/13 passing against a real,
|
||||
freshly-built stack: real Platform API/Gateway/Provisioner/disposable Lab, real shell, real
|
||||
nested Docker, real Git, real k3s (left completely untouched, still healthy at the end). Walks
|
||||
one genuinely representative real-Lab task per new course (a real nginx-inspection Lab for ROOT
|
||||
02, a real merge-conflict resolution for ROOT 03, a real container-networking diagnose-and-fix
|
||||
for ROOT 04) plus the full ROOT 01-04 chain to a genuine ROOT 05 unlock, with an instructor
|
||||
narrative confirming the resulting learning state is visible.
|
||||
|
||||
## Regression
|
||||
|
||||
Full fresh-stack reruns after every fix: 6A (23/23), 6B (26/26), 6C (17/17), 6D (20/20), 6E
|
||||
(23/23) — all green. 2A/2B/3A/3B/5A/5B/5C not re-run: `git diff` for this phase touches only
|
||||
`platform/api/src/course-routes.js` (the one real application-code change) plus content/
|
||||
migrations/tests/docs — confirmed by grep that none of those earlier suites reference the
|
||||
`/api/student/activities/:id/complete` route this phase's fix touched.
|
||||
|
||||
## Frontend, Lab image, nested Docker, k3s
|
||||
|
||||
No frontend change was needed — `CourseView.jsx`/`CurriculumView.jsx` already generalize
|
||||
correctly to every ROOT 02-04 activity/status shape (proven by the rehearsal and e2e suite, not
|
||||
assumed). Frontend build: clean, unchanged this phase. Lab image: rebuilt with the dash-fix and
|
||||
new scenario files, standalone-smoke-tested and exercised extensively via the rehearsal (real
|
||||
nested Docker + real k3s side by side, k3s health reconfirmed untouched at the rehearsal's end).
|
||||
|
||||
## New application code and why necessary
|
||||
|
||||
`platform/api/src/course-routes.js` — the lock-enforcement fix described above. This is the
|
||||
ONLY application code changed this phase; every course/content requirement was satisfiable with
|
||||
the existing `course_modules`/`module_activities`/`assessment_definitions`/scenario-engine
|
||||
model.
|
||||
|
||||
## Content validation
|
||||
|
||||
`infra/scripts/validate-course-content.js` — 0 errors, all courses/modules with real content,
|
||||
the full activity/scenario set including this phase's 17 new scenario files (6 Linux task
|
||||
scenarios, 2 Linux MCQs, 3 Git task scenarios, 2 Git MCQs, 5 Docker task scenarios, 2 Docker
|
||||
MCQs — see the migrations for the exact counts per course).
|
||||
|
||||
## Known limitations
|
||||
|
||||
- Instructor-led vs. self-paced timing remains prose in Instructor Notes, not a separate
|
||||
database field (unchanged from Phase 6D).
|
||||
- Three practice-bank modules from earlier phases (root-06-m01, root-07-m01, root-10-m02) remain
|
||||
instructor-led-only this pilot — unrelated to Phase 6E's own scope, unchanged.
|
||||
- No placement testing or instructor-override UI (explicitly out of scope, as in every prior
|
||||
phase) — the extension point (`courses.prerequisites` as a plain array) remains documented in
|
||||
`docs/ROOT_FOUNDATIONS_ARCHITECTURE.md`.
|
||||
|
||||
## Recommended Phase 6F scope
|
||||
|
||||
With the entire ROOT_ foundational-to-capstone path now genuinely READY, natural next
|
||||
directions: author ROOT 08 (Storage & Scheduling) to close the last PARTIAL Kubernetes course;
|
||||
build the future lab infrastructure ROOT 11 modules 2-7 need (a Git+ArgoCD environment for
|
||||
GitOps, a CI runner for CI/CD) and author that content; or invest in self-paced teaching layers
|
||||
for the three remaining instructor-led-only practice-bank modules.
|
||||
@@ -14,9 +14,25 @@ new hands-on Linux scenarios plus 2 MCQs, all READY. This is the program's first
|
||||
the Kubernetes/CKS content this matrix was originally scoped to, so it is documented separately
|
||||
in `docs/ROOT_FOUNDATIONS_ARCHITECTURE.md` and `docs/PHASE6D_ROOT_FOUNDATIONS.md` rather than
|
||||
folded into the tables below, which remain the ROOT 05+ (Kubernetes) scenario-coverage record.
|
||||
Post-Phase-6D totals: **28 modules total across all courses with any content — 22 READY, 6
|
||||
PARTIAL** (Phase 6C's 22 modules — 16 READY, 6 PARTIAL — plus 6 new ROOT 01 modules, all READY).
|
||||
**106 scenario files** (98 + 8 new).
|
||||
Post-Phase-6D totals: 28 modules total across all courses with any content — 22 READY, 6
|
||||
PARTIAL (Phase 6C's 22 modules — 16 READY, 6 PARTIAL — plus 6 new ROOT 01 modules, all READY).
|
||||
106 scenario files (98 + 8 new).
|
||||
|
||||
## Phase 6E addition
|
||||
|
||||
Phase 6E authored ROOT 02 (Linux Administration & Networking, 6 modules, 37 activities), ROOT 03
|
||||
(Git & Collaborative Development, 6 modules, 31 activities), and ROOT 04 (Containers & Docker, 8
|
||||
modules, 43 activities) in full — completing the entire ROOT_ Foundations sequence
|
||||
(ROOT 01-04). See `docs/PHASE6E_ROOT_FOUNDATIONS_COMPLETION.md` and
|
||||
`docs/ROOT_FOUNDATIONS_PILOT_READINESS.md`. 20 new hands-on/MCQ scenarios (6 Linux task + 2
|
||||
Linux MCQ, 3 Git task + 2 Git MCQ, 5 Docker task + 2 Docker MCQ).
|
||||
|
||||
**Post-Phase-6E totals: 48 modules total across all courses with any content — 42 READY, 6
|
||||
PARTIAL** (unchanged PARTIAL set: root-06-m01, root-07-m01, root-08-m01, root-08-m02,
|
||||
root-10-m02, root-11-m01 — none touched by this phase). **126 scenario files** (106 + 20 new).
|
||||
The entire ROOT_ program now has a complete, READY, real path from Computing & Linux
|
||||
Foundations through the Capstone, excluding only the explicitly-deferred ROOT 08 and ROOT 11
|
||||
modules 2-7 — see `docs/ROOT_PILOT_CURRICULUM.md`.
|
||||
|
||||
## Phase 6C verification (superseding Phase 6B's own self-reported counts)
|
||||
|
||||
|
||||
@@ -7,6 +7,12 @@ activity type or content model was needed. Every course below is a row that alre
|
||||
`courses` (`status='planned'`, seeded by Phase 6A's `022_course_content.sql`) — this phase adds
|
||||
their modules and activities, and updates their `status`/`objectives`/`prerequisites`.
|
||||
|
||||
**Status as of Phase 6E:** this document was written as a DESIGN in Phase 6D, when only ROOT 01
|
||||
was actually authored. As of Phase 6E, all four courses below (ROOT 01-04) are fully authored
|
||||
and READY exactly as designed here — see `docs/PHASE6E_ROOT_FOUNDATIONS_COMPLETION.md` and
|
||||
`docs/ROOT_FOUNDATIONS_PILOT_READINESS.md` for the built/verified state. The module tables below
|
||||
remain accurate as the as-built architecture, not just the original plan.
|
||||
|
||||
## Course sequence and prerequisite chain
|
||||
|
||||
```
|
||||
|
||||
@@ -1,65 +1,76 @@
|
||||
# ROOT Foundations Pilot Readiness — ROOT 01-04
|
||||
|
||||
Phase 6D §24 — the honest READY/PARTIAL/DEFERRED classification for ROOT 01-04, using the same
|
||||
bar `docs/ROOT_PILOT_READINESS_STANDARD.md` established in Phase 6C. The standard is not
|
||||
lowered because these courses are new.
|
||||
Phase 6E §27 — the honest, re-audited READY/PARTIAL/DEFERRED classification for ROOT 01-04,
|
||||
using the same bar `docs/ROOT_PILOT_READINESS_STANDARD.md` established in Phase 6C. Phase 6D's
|
||||
ROOT 01 classification is not inherited blindly — it is revalidated here against the same
|
||||
standard, using the same evidence discipline (real API verification, not assumption).
|
||||
|
||||
## ROOT 01 — Computing & Linux Foundations: READY
|
||||
## ROOT 01 — Computing & Linux Foundations: READY (revalidated)
|
||||
|
||||
All 6 modules re-confirmed READY this phase — no ROOT 01 content itself changed, but its real
|
||||
prerequisite gate to ROOT 02 was verified for real (previously it could only be vacuously
|
||||
satisfied, since ROOT 02 didn't exist). `e2e-phase6d.js` and `e2e-phase6e.js` both independently
|
||||
re-prove ROOT 01's full lesson→lab→assessment→completion path end to end, fresh, this phase.
|
||||
|
||||
## ROOT 02 — Linux Administration & Networking: READY
|
||||
|
||||
All 6 modules meet the 8-point standard:
|
||||
|
||||
| Module | Status | Evidence |
|
||||
|---|---|---|
|
||||
| M1 How Computers Run Software | READY | Lesson, guided exercise, real hands-on lab (`linux-inspect-hardware`), reflection. Verified via `e2e-phase6d.js` and the real-Lab rehearsal (`infra/scripts/drills/test-phase6d-foundations-rehearsal.js`). |
|
||||
| M2 Linux and the Shell | READY | Full gold pattern (lesson/demo/kc/guided/lab/reflection). Lab (`linux-shell-basics`) tested end-to-end via the real scenario-validation API, including a negative test (fails when unsolved). |
|
||||
| M3 Navigating the Filesystem | READY | Full gold pattern. Lab (`linux-filesystem-navigation`) tested end-to-end. |
|
||||
| M4 Files and Text | READY | Full gold pattern. Lab (`linux-files-and-text`) tested end-to-end. |
|
||||
| M5 Processes | READY | Full gold pattern. Lab (`linux-process-management`) tested end-to-end, including a negative test (fails while the process is still running). |
|
||||
| M6 Users & Permissions Foundations | READY | Full gold pattern. Lab (`linux-permissions-foundations`) tested end-to-end via the real-Lab rehearsal, including a REAL permission denial (a second Linux user genuinely denied read access) before the fix. |
|
||||
| M1 Linux System Administration | READY | Lesson/demo/kc/guided + 2 real labs (`linux-service-inspection` — inspects the platform's own real nginx without touching it; `linux-service-lifecycle` — start/verify/stop a real service) + reflection. Both labs tested end-to-end via the real scenario-validation API, including negative tests. |
|
||||
| M2 Storage & Filesystems | READY | Full gold pattern. `linux-storage-inspection` tested end-to-end, negative-tested. |
|
||||
| M3 Networking Mental Model | READY | The course's "critical module," given extra lesson depth. No lab by design (conceptual mastery via knowledge check + guided exercise), matching `root-05-m00`'s precedent. |
|
||||
| M4 Linux Network Inspection | READY | Full gold pattern. `linux-network-inspection` tested end-to-end. |
|
||||
| M5 DNS & HTTP | READY | Full gold pattern. `linux-dns-http-diagnosis` (a genuine port-mismatch diagnose-and-fix) tested end-to-end, negative-tested. |
|
||||
| M6 Linux Troubleshooting | READY | Full gold pattern. `linux-troubleshooting-framework` (a real missing-config-file failure, diagnosed via the RootNode framework) tested end-to-end, negative-tested. |
|
||||
|
||||
**Self-paced readiness:** confirmed programmatically — `infra/scripts/validate-course-content.js`'s
|
||||
deferral-phrase check and `e2e-phase6d.js`'s equivalent live check both scan every ROOT 01
|
||||
activity's student-visible content for "ask your instructor" or equivalent and found none.
|
||||
**Assessment:** `rootnode-linux-networking-assessment` wired and verified live (2 required + 4
|
||||
random scenarios, 6 total).
|
||||
|
||||
**Instructor-led readiness:** every lesson/demonstration/guided_exercise/reflection activity has
|
||||
a real Instructor Notes section (timing, misconceptions, discussion questions), verified hidden
|
||||
from students both statically and via a live API check.
|
||||
## ROOT 03 — Git & Collaborative Development: READY
|
||||
|
||||
**Assessment:** `rootnode-linux-foundations-assessment` wired and verified live (real draw: 2
|
||||
required hands-on tasks + 2 random hands-on tasks + 2 random MCQs, 6 scenarios total).
|
||||
| Module | Status | Evidence |
|
||||
|---|---|---|
|
||||
| M1 Why Version Control Exists | READY | Conceptual opener (lesson/kc/reflection) — no lab needed, motivational content. |
|
||||
| M2 Git Mental Model | READY | Full teaching pattern, no lab by design (model before commands, matching ROOT 02 M3's precedent). |
|
||||
| M3 Everyday Git | READY | `git-everyday-basics` — a REAL local Git repository, init/add/commit, validated by inspecting actual `git log`/`git show` state, not just command output strings. Tested end-to-end, negative-tested. |
|
||||
| M4 Branching & Merging | READY | `git-branching-and-merge` — a REAL merge conflict, caused on purpose (two branches editing the same line), resolved by the student, validated by inspecting the real merge commit and confirming conflict markers are gone. Tested end-to-end (including via the real-Lab rehearsal), negative-tested. |
|
||||
| M5 Remote Repositories | READY | `git-remote-clone-push` — a REAL local bare repository standing in for a remote; clone/commit/push validated by inspecting the REMOTE side directly, not just the student's clone. No account, no network, no RootNode credential ever used. Tested end-to-end. |
|
||||
| M6 Engineering Git Workflow | READY | Conceptual closer bridging to CI/CD/GitOps — no lab by design. |
|
||||
|
||||
## ROOT 02 — Linux Administration & Networking: DEFERRED
|
||||
**Assessment:** `rootnode-git-fundamentals-assessment` wired and verified live (2 required + 3
|
||||
random scenarios, 5 total). All state-validated via real `git` commands against real
|
||||
repositories, not string-matched command output alone (Step 6's explicit requirement).
|
||||
|
||||
Architecture fully designed in `docs/ROOT_FOUNDATIONS_ARCHITECTURE.md` (6 modules, including the
|
||||
explicitly "critical" Networking Mental Model module). Zero modules authored this phase — course
|
||||
remains `status = 'planned'`, honestly, with a real (not fabricated) `prerequisites` gate to
|
||||
ROOT 01 already in place so the sequence is correct once it IS authored.
|
||||
## ROOT 04 — Containers & Docker: READY
|
||||
|
||||
## ROOT 03 — Git & Collaborative Development: DEFERRED
|
||||
| Module | Status | Evidence |
|
||||
|---|---|---|
|
||||
| M1 Why Containers Exist | READY | Conceptual opener, no lab by design. |
|
||||
| M2 Images & Containers | READY | The image/container distinction, no lab by design (model before commands). |
|
||||
| M3 Docker CLI | READY | `docker-cli-basics` — real `docker run`/`exec`/`inspect` against the nested Docker Engine, validated by inspecting real container state. Tested end-to-end, negative-tested. |
|
||||
| M4 Container Networking | READY | `docker-container-networking` — a REAL "works internally, unreachable from host" failure (no port published), diagnosed and fixed by recreating the container with `-p`. Tested end-to-end via the real-Lab rehearsal AND negative-tested. |
|
||||
| M5 Container Storage | READY | `docker-container-storage` — a REAL destroy/recreate cycle proving named-volume persistence, not just described. Tested end-to-end. |
|
||||
| M6 Dockerfile & Image Build | READY | `docker-build-image` — a real Dockerfile, a real build, a real custom image run. Tested end-to-end. |
|
||||
| M7 Docker Compose | READY | `docker-compose-multiservice` — a real two-service Compose application, real `docker compose up`, validated via real service state and a real published port. Tested end-to-end. |
|
||||
| M8 Containers → Kubernetes | READY | The explicit bridge module — no lab by design (honest: the course's job is naming what Kubernetes adds, not re-teaching Kubernetes itself). |
|
||||
|
||||
Architecture designed (6 modules, local-repository-only strategy documented in
|
||||
`docs/ROOT_FOUNDATIONS_LAB_STRATEGY.md` §4). Zero modules authored this phase.
|
||||
**Assessment:** `rootnode-containers-assessment` wired and verified live (2 required + 4 random
|
||||
scenarios, 6 total).
|
||||
|
||||
## ROOT 04 — Containers & Docker: DEFERRED (content) / PLATFORM EXTENSION DONE
|
||||
## The real ROOT 01→02→03→04→05 chain
|
||||
|
||||
Architecture designed (8 modules). **The platform extension ROOT 04 needs — a nested Docker
|
||||
Engine inside the Lab — was built and verified this phase** (`docs/ROOT_FOUNDATIONS_LAB_STRATEGY.md`
|
||||
§3: real `docker run`, `docker build`, and `docker compose` all tested working inside the actual
|
||||
`rootnode-academy:test` image under production resource constraints). No course content was
|
||||
authored against it yet — that's a real, substantial content-authoring effort left for a future
|
||||
phase, now unblocked by this phase's platform work.
|
||||
|
||||
## Why ROOT 02-04 were not authored this phase
|
||||
|
||||
Given realistic effort constraints in a single phase, authoring, testing, and verifying ROOT 01
|
||||
to the same rigor as every ROOT 05+ module (real scenarios, live end-to-end verification, a
|
||||
real-Lab rehearsal, an assessment) was prioritized over shallow architecture-only coverage
|
||||
across all four courses. This mirrors Phase 6C's own precedent: some content built to a genuine
|
||||
READY bar, the rest explicitly and honestly DEFERRED with a real reason, rather than every
|
||||
course padded to look further along than it is (`e2e-phase6d.js`'s own first test explicitly
|
||||
asserts ROOT 02-04 have zero modules, catching any future accidental over-claim).
|
||||
Verified live, repeatedly, this phase: a fresh student sees ROOT 02 genuinely LOCKED (not
|
||||
vacuously satisfied — ROOT 01 has real content now) until ROOT 01 completes; the same holds at
|
||||
every subsequent boundary through ROOT 05. Direct API attempts to bypass this were tested and
|
||||
(after a real bug was found and fixed — see `docs/PHASE6E_ROOT_FOUNDATIONS_COMPLETION.md`)
|
||||
correctly rejected with `403 locked`.
|
||||
|
||||
## Honest totals
|
||||
|
||||
**READY: 1 course (ROOT 01), 6 modules. DEFERRED: 3 courses (ROOT 02-04), 0 modules — by
|
||||
design, not oversight.**
|
||||
**READY: 4 of 4 courses (ROOT 01, 02, 03, 04), 26 of 26 modules.** Every module in the ROOT
|
||||
Foundations sequence meets the same readiness bar — self-paced sufficient (verified: no
|
||||
"ask your instructor" language anywhere in student-visible content), instructor-notes covered,
|
||||
real hands-on labs with real state validation where hands-on work is the right teaching tool,
|
||||
and honest conceptual-only modules where the subject genuinely doesn't need one.
|
||||
|
||||
@@ -7,24 +7,26 @@ marketed as more finished than it is. See
|
||||
means and [`docs/ROOT11_DEVOPS_PLATFORM_SCOPE.md`](ROOT11_DEVOPS_PLATFORM_SCOPE.md) for the
|
||||
ROOT 11 scope decision referenced below.
|
||||
|
||||
## Pilot delivery mode: instructor-led (cohort-based)
|
||||
## Pilot delivery mode: instructor-led (cohort-based), with a fully self-paced foundation
|
||||
|
||||
The first pilot is scoped as **instructor-led**, not fully self-paced. Every module below that
|
||||
is marked READY meets the self-paced bar (no "your instructor will explain this" gaps). A
|
||||
smaller set of modules — flagged explicitly — are READY for instructor-led delivery only,
|
||||
because they are dense hands-on practice banks whose sibling module in the same course already
|
||||
carries the taught concept; running them live lets an instructor supply framing a self-paced
|
||||
student wouldn't get. Fully self-paced delivery of those specific modules is deferred to a
|
||||
future phase pending an authored teaching layer for each. See Section 16 discussion below.
|
||||
The first pilot is scoped as **instructor-led** as its recommended default, not because most of
|
||||
the curriculum requires it — as of Phase 6E, ROOT 01-04 (the entire foundational half) and every
|
||||
Kubernetes/security/troubleshooting/real-ops/capstone module EXCEPT three specific practice-bank
|
||||
modules meet the full self-paced bar (no "your instructor will explain this" gaps, verified
|
||||
programmatically). Those three flagged modules — dense hands-on practice banks whose sibling
|
||||
module in the same course already carries the taught concept — are READY for instructor-led
|
||||
delivery only; running them live lets an instructor supply framing a self-paced student wouldn't
|
||||
get. Fully self-paced delivery of those specific modules is deferred to a future phase pending an
|
||||
authored teaching layer for each. See Section 16 discussion below.
|
||||
|
||||
## Program: ROOT_ (root-kubernetes-foundations)
|
||||
|
||||
| Course | Pilot status | Notes |
|
||||
|---|---|---|
|
||||
| ROOT 01 Computing & Linux Foundations | **IN PILOT** (added Phase 6D) | Fully authored, READY — 6 modules, real hands-on Linux Labs, a wired assessment. See `docs/PHASE6D_ROOT_FOUNDATIONS.md`. |
|
||||
| ROOT 02 Linux Administration & Networking | **NOT IN PILOT** | Architecture designed (`docs/ROOT_FOUNDATIONS_ARCHITECTURE.md`), content not yet authored. |
|
||||
| ROOT 03 Git & Collaborative Development | **NOT IN PILOT** | Architecture designed, content not yet authored. |
|
||||
| ROOT 04 Containers & Docker | **NOT IN PILOT** | Architecture designed; its platform extension (nested Docker Engine) is built and tested, content not yet authored. |
|
||||
| ROOT 02 Linux Administration & Networking | **IN PILOT** (added Phase 6E) | Fully authored, READY — 6 modules, real hands-on Linux/networking Labs, a wired assessment. See `docs/PHASE6E_ROOT_FOUNDATIONS_COMPLETION.md`. |
|
||||
| ROOT 03 Git & Collaborative Development | **IN PILOT** (added Phase 6E) | Fully authored, READY — 6 modules, real local-Git Labs (including a real merge conflict and a real local-bare-remote push), a wired assessment. |
|
||||
| ROOT 04 Containers & Docker | **IN PILOT** (added Phase 6E) | Fully authored, READY — 8 modules using the nested Docker Engine (built Phase 6D), real hands-on container Labs, a wired assessment. |
|
||||
| ROOT 05 Kubernetes Foundations | **IN PILOT** | See modules below. |
|
||||
| ROOT 06 Kubernetes Application Workloads | **IN PILOT** | See modules below. |
|
||||
| ROOT 07 Kubernetes Networking & Configuration | **IN PILOT** | See modules below. |
|
||||
@@ -47,6 +49,38 @@ future phase pending an authored teaching layer for each. See Section 16 discuss
|
||||
| root-01-m05-processes | READY | m04 | Inspect and correctly terminate a real process | Self-paced or instructor-led | 6 required | 1 lab |
|
||||
| root-01-m06-users-and-permissions-foundations | READY | m05 | Apply rwx permissions and the least-privilege principle | Self-paced or instructor-led | 7 required | 1 lab + **ROOT 01 Linux Foundations Assessment** (6-scenario timed exam) |
|
||||
|
||||
### ROOT 02 — Linux Administration & Networking (added Phase 6E)
|
||||
| Module | Status | Prerequisite | Outcome | Delivery | Required/Optional | Labs/Assessment |
|
||||
|---|---|---|---|---|---|---|
|
||||
| root-02-m01-linux-system-administration | READY | ROOT 01 | Answer the five-question service checklist for a real service | Self-paced or instructor-led | 7 required | 2 labs |
|
||||
| root-02-m02-storage-and-filesystems | READY | m01 | Distinguish df from du; find real disk usage | Self-paced or instructor-led | 6 required | 1 lab |
|
||||
| root-02-m03-networking-mental-model | READY | m02 | The critical networking model — no lab by design | Self-paced or instructor-led | 5 required | none (conceptual) |
|
||||
| root-02-m04-linux-network-inspection | READY | m03 | Use ip/ss/getent/curl to answer specific diagnostic questions | Self-paced or instructor-led | 6 required | 1 lab |
|
||||
| root-02-m05-dns-and-http | READY | m04 | Categorize a failure as DNS/routing/TCP/application | Self-paced or instructor-led | 6 required | 1 lab |
|
||||
| root-02-m06-linux-troubleshooting | READY | m05 | Apply the RootNode framework to a real Linux failure | Self-paced or instructor-led | 7 required | 1 lab + **ROOT 02 Linux Administration & Networking Assessment** (6-scenario timed exam) |
|
||||
|
||||
### ROOT 03 — Git & Collaborative Development (added Phase 6E)
|
||||
| Module | Status | Prerequisite | Outcome | Delivery | Required/Optional | Labs/Assessment |
|
||||
|---|---|---|---|---|---|---|
|
||||
| root-03-m01-why-version-control-exists | READY | ROOT 02 | Explain what version control gives you beyond copying files | Self-paced or instructor-led | 3 required | none (conceptual) |
|
||||
| root-03-m02-git-mental-model | READY | m01 | The working-tree/staging/repository model — no lab by design | Self-paced or instructor-led | 5 required | none (conceptual) |
|
||||
| root-03-m03-everyday-git | READY | m02 | init/status/add/commit/log/diff fluently | Self-paced or instructor-led | 6 required | 1 lab (real repository state) |
|
||||
| root-03-m04-branching-and-merging | READY | m03 | Cause and resolve a real merge conflict | Self-paced or instructor-led | 6 required | 1 lab (real conflict) |
|
||||
| root-03-m05-remote-repositories | READY | m04 | Clone/commit/push to a real local-bare remote | Self-paced or instructor-led | 6 required | 1 lab |
|
||||
| root-03-m06-engineering-git-workflow | READY | m05 | Small-commit/review/protect-main discipline; bridge to CI/CD/GitOps | Self-paced or instructor-led | 4 required | none (conceptual) + **ROOT 03 Git & Collaborative Development Assessment** (5-scenario timed exam) |
|
||||
|
||||
### ROOT 04 — Containers & Docker (added Phase 6E)
|
||||
| Module | Status | Prerequisite | Outcome | Delivery | Required/Optional | Labs/Assessment |
|
||||
|---|---|---|---|---|---|---|
|
||||
| root-04-m01-why-containers-exist | READY | ROOT 03 | The dependency-inconsistency problem; VM vs. container | Self-paced or instructor-led | 4 required | none (conceptual) |
|
||||
| root-04-m02-images-and-containers | READY | m01 | The image/container distinction precisely | Self-paced or instructor-led | 5 required | none (conceptual) |
|
||||
| root-04-m03-docker-cli | READY | m02 | run/exec/ps/logs/inspect/stop/rm fluently | Self-paced or instructor-led | 6 required | 1 lab |
|
||||
| root-04-m04-container-networking | READY | m03 | Diagnose and fix a real "unreachable from host" failure | Self-paced or instructor-led | 6 required | 1 lab |
|
||||
| root-04-m05-container-storage | READY | m04 | Prove volume persistence with a real destroy/recreate cycle | Self-paced or instructor-led | 6 required | 1 lab |
|
||||
| root-04-m06-dockerfile-and-image-build | READY | m05 | Write a real Dockerfile; build a real image | Self-paced or instructor-led | 6 required | 1 lab |
|
||||
| root-04-m07-docker-compose | READY | m06 | Run a real multi-service application declaratively | Self-paced or instructor-led | 6 required | 1 lab |
|
||||
| root-04-m08-containers-to-kubernetes | READY | m07 | Explain what Kubernetes adds beyond Docker — the explicit bridge to ROOT 05 | Self-paced or instructor-led | 3 required | none (conceptual) + **ROOT 04 Containers & Docker Assessment** (6-scenario timed exam) |
|
||||
|
||||
### ROOT 05 — Kubernetes Foundations
|
||||
| Module | Status | Prerequisite | Outcome | Delivery | Required/Optional | Labs/Assessment |
|
||||
|---|---|---|---|---|---|---|
|
||||
@@ -109,8 +143,6 @@ instructor-only prep material was created outside the per-activity Instructor No
|
||||
|
||||
## What is intentionally NOT in the first pilot
|
||||
|
||||
- ROOT 02-04 (architecture designed in Phase 6D, content not yet authored — ROOT 01 IS now in
|
||||
the pilot, added Phase 6D).
|
||||
- ROOT 08 Kubernetes Storage & Scheduling (non-capstone-gating; both modules remain PARTIAL).
|
||||
- ROOT 11 modules 2-7 (GitOps, CI/CD, IaC, hands-on Observability, hands-on Reliability) — see
|
||||
scope doc.
|
||||
@@ -123,8 +155,12 @@ instructor-only prep material was created outside the per-activity Instructor No
|
||||
|
||||
## Bottom line
|
||||
|
||||
Every module the pilot's required completion path depends on (ROOT 01, 05-07, 09-10, 12,
|
||||
Capstone) meets the READY bar for instructor-led delivery, and ROOT 01's self-paced readiness
|
||||
was verified explicitly (see `docs/PHASE6D_ROOT_FOUNDATIONS.md`). Three specific modules run
|
||||
instructor-led only this pilot. ROOT 02-04, ROOT 08, and most of ROOT 11 are explicitly out of
|
||||
the required path. Given that scope, the pilot curriculum, delivered instructor-led, is ready.
|
||||
The ROOT_ pilot now runs a complete, beginning-to-advanced path: ROOT 01-04 (Linux → networking
|
||||
→ Git → containers), ROOT 05-07/09-10 (Kubernetes), ROOT 12 (Real Ops), and the Capstone — every
|
||||
module in that path meets the READY bar, and ROOT 01-04's self-paced readiness was verified
|
||||
explicitly this phase (real API tests, a real-Lab rehearsal covering real Linux/Docker/Git
|
||||
actions, and a programmatic scan for instructor-deferral language across every module). Three
|
||||
specific practice-bank modules (root-06-m01, root-07-m01, root-10-m02) run instructor-led only
|
||||
this pilot. ROOT 08 and most of ROOT 11 are explicitly out of the required path. Given that
|
||||
scope, the pilot curriculum — genuinely self-paced-capable end to end, with instructor-led as
|
||||
the recommended default for a first cohort — is ready.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user