Compare commits
33
Commits
b6516a74db
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6a5902ddf | ||
|
|
bfae2aafe0 | ||
|
|
74033b4cb8 | ||
|
|
0b2303a3e2 | ||
|
|
a4c0058f8c | ||
|
|
fbf52d8334 | ||
|
|
c86b3de611 | ||
|
|
77fd1869db | ||
|
|
f0ff14af38 | ||
|
|
80a5f6e457 | ||
|
|
281d60bc2d | ||
|
|
8b3e56e402 | ||
|
|
f0dde9b84a | ||
|
|
16622bd9ab | ||
|
|
b0e07bb17f | ||
|
|
788fb197e0 | ||
|
|
aa63d09fb8 | ||
|
|
40362ec0c9 | ||
|
|
24eee80354 | ||
|
|
df5e39f767 | ||
|
|
7d870d8e2b | ||
|
|
3510979196 | ||
|
|
d066410451 | ||
|
|
bd55b74b4f | ||
|
|
2864fe03dc | ||
|
|
f695b7c95d | ||
|
|
e48710a0be | ||
|
|
87ad1251ce | ||
|
|
59edceea1f | ||
|
|
a09b7aaa53 | ||
|
|
682ef2de47 | ||
|
|
e68b6f445a | ||
|
|
e007bcf000 |
+66
@@ -0,0 +1,66 @@
|
||||
# Welcome to ROOT_
|
||||
|
||||
ROOT_ is RootNode Academy's path from "I can use a computer" to "I can run and repair real
|
||||
infrastructure." It's built for anyone roughly high-school age and up — including college
|
||||
students, career changers, help-desk technicians, junior sysadmins, and working developers who
|
||||
want to move into DevOps, cloud, or platform engineering. There's no strict age cutoff: what
|
||||
matters is whether you're ready for the material, not how old you are.
|
||||
|
||||
## Where ROOT_ goes
|
||||
|
||||
```
|
||||
ROOT 01 Computing & Linux Foundations
|
||||
ROOT 02 Linux Administration & Networking
|
||||
ROOT 03 Git & Collaborative Development
|
||||
ROOT 04 Containers & Docker
|
||||
ROOT 05 Kubernetes Foundations
|
||||
ROOT 06 Kubernetes Application Workloads
|
||||
ROOT 07 Kubernetes Networking & Configuration
|
||||
ROOT 09 Kubernetes Security
|
||||
ROOT 10 Kubernetes Troubleshooting & Observability
|
||||
ROOT 12 RootNode Real Ops
|
||||
CAPSTONE Operate and Recover a Production-Like Environment
|
||||
```
|
||||
|
||||
Each course builds on real skill from the one before it — you won't be asked to use a tool or
|
||||
concept that a previous course didn't already teach you. By the end, you'll have operated a real
|
||||
Kubernetes cluster, diagnosed and fixed real incidents, and demonstrated it in a hands-on
|
||||
capstone — not answered multiple-choice questions about it.
|
||||
|
||||
## How you'll actually learn
|
||||
|
||||
Every module follows the same rhythm:
|
||||
|
||||
**Learn → Practice → Lab → Troubleshoot → Validate**
|
||||
|
||||
- **Learn** — a short lesson introduces one real concept, plainly, with no unexplained jargon.
|
||||
- **Practice** — a demonstration or guided exercise lets you see the concept applied before
|
||||
you're on your own with it.
|
||||
- **Lab** — you do the real thing yourself, in your own disposable Lab environment: a real
|
||||
Linux shell, real Git, real Docker, and (from ROOT 05 onward) a real Kubernetes cluster.
|
||||
- **Troubleshoot** — Labs are built to sometimes not go as planned on the first try, on purpose.
|
||||
Figuring out why something didn't work is as much the skill as doing it right the first time.
|
||||
- **Validate** — your Lab checks its own result. You'll know immediately whether you actually
|
||||
solved it, not just whether you think you did.
|
||||
|
||||
## Self-paced or instructor-led
|
||||
|
||||
ROOT_ is built to work either way. If you're going through it on your own, every module gives you
|
||||
everything you need without a teacher in the room. If you're in an instructor-led class or camp,
|
||||
your instructor can see your progress, help when you're stuck, and add their own pacing and
|
||||
discussion on top of the same real curriculum — nothing about the platform changes between the
|
||||
two, only who else is watching your progress.
|
||||
|
||||
## Assessments and the Capstone
|
||||
|
||||
A few courses end in a short formal assessment — a handful of real, timed Lab tasks (not a
|
||||
multiple-choice quiz) that check you can actually do what the course taught, not just recognize
|
||||
the right answer. The program ends with the **Capstone**: no more new teaching, just a
|
||||
production-like environment with real incidents to find and fix, the same way you would on the
|
||||
job.
|
||||
|
||||
## Your progress is permanent — your Lab is not
|
||||
|
||||
One thing to know before you start: the Lab environment you work in is disposable and can be
|
||||
recreated at any time. Your academic progress is not. The next module covers exactly what that
|
||||
means and why it's nothing to worry about.
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
# Your Disposable Lab
|
||||
|
||||
Every hands-on Lab in ROOT_ gives you a real, isolated environment — a real Linux shell from
|
||||
ROOT 01 onward, real Git and Docker from ROOT 03-04, and a real Kubernetes cluster from ROOT 05
|
||||
onward. It's yours alone while you're working in it. Here's what you need to know before your
|
||||
first one.
|
||||
|
||||
## Disposable means disposable
|
||||
|
||||
Your Lab environment can be recreated at any time — when you start a new Lab, when an
|
||||
instructor resets a classroom session, or if the environment needs to be refreshed. When that
|
||||
happens, **anything that only exists inside the Lab disappears**: files you created in the
|
||||
terminal, a local Git repository you initialized, a Docker container or image you built, a
|
||||
Kubernetes object you deployed. None of that is backed up, and none of it needs to be — it was
|
||||
never the point.
|
||||
|
||||
## Your academic progress is not disposable
|
||||
|
||||
What IS permanent, stored on RootNode's servers the moment you earn it: which lessons you've
|
||||
read, which Labs you've completed, your assessment results, and your position in the course
|
||||
sequence. Recreating your Lab never erases any of that. You can lose a Lab entirely and pick up
|
||||
exactly where you left off in the course.
|
||||
|
||||
## How you know you actually solved it
|
||||
|
||||
Every Lab task validates itself — when you think you're done, you run the check and get a real
|
||||
pass/fail answer immediately, based on the real state of your environment, not on your own
|
||||
guess. If it fails, that's useful information, not a penalty: it's telling you specifically what
|
||||
isn't right yet.
|
||||
|
||||
## If you get stuck
|
||||
|
||||
Most Labs have hints available, revealed one at a time so you can try the next step yourself
|
||||
before seeing more. Using a hint doesn't cost you anything or affect your result — it's there to
|
||||
keep you moving, not to be avoided on principle. A formal, timed assessment may disable hints;
|
||||
if so, the assessment will tell you plainly before you start.
|
||||
|
||||
If a Lab's environment itself seems broken rather than the task being hard (a command that
|
||||
should work doesn't, the environment won't start), use **Reset Scenario & Environment** to tear
|
||||
down and rebuild it cleanly rather than fighting a broken environment.
|
||||
|
||||
## If you're in an instructor-led session
|
||||
|
||||
Your instructor can see your progress in real time and gets notified the moment you use **Need
|
||||
Help** — no need to wait to be noticed. Use it for anything: a Lab that seems broken, a concept
|
||||
that didn't click, or just wanting a second pair of eyes before you second-guess a working
|
||||
solution.
|
||||
|
||||
## Returning to the course
|
||||
|
||||
When you're done with a Lab — pass or still stuck — you can always return to the course view and
|
||||
come back to it later. Nothing about leaving a Lab mid-task loses your place.
|
||||
|
||||
You're ready. Let's start with how a computer actually runs the software you're about to spend
|
||||
this program controlling.
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
# Knowledge Check: Welcome to ROOT_
|
||||
|
||||
---
|
||||
|
||||
**1. If your Lab environment is recreated, what happens to a file you saved inside it, and
|
||||
what happens to your completed Labs so far?**
|
||||
|
||||
*Answer:* The file disappears — nothing inside the Lab persists across a recreation. Your
|
||||
completed Labs and course progress are unaffected — those are stored on RootNode's servers, not
|
||||
in the Lab, and survive regardless of what happens to any Lab environment.
|
||||
|
||||
**2. A Lab task fails your validation check. What should that tell you?**
|
||||
|
||||
*Answer:* Specifically what isn't correct yet, based on the real current state of your
|
||||
environment — it's diagnostic information to act on, not a penalty.
|
||||
|
||||
**3. Name one thing to try before assuming a Lab is "just broken."**
|
||||
|
||||
*Answer:* Check the available hints (revealed one at a time), and if the environment itself
|
||||
genuinely seems broken rather than the task being hard, use Reset Scenario & Environment to
|
||||
rebuild it cleanly.
|
||||
|
||||
---
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** This is a good moment to actually demonstrate a Lab reset live if you have
|
||||
time — students remember "I saw it happen and nothing bad occurred" far better than being told
|
||||
it's safe.
|
||||
|
||||
**Approximate timing:** 3 minutes.
|
||||
+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.
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
# Practice Bank: Deployments & Workloads
|
||||
|
||||
This module is different from the ones before it. There's no new lesson here — you already got
|
||||
the real teaching in **Deployments, Scaling & Updates** and **Jobs, CronJobs & DaemonSets**. This
|
||||
is eight short, focused Labs that drill the exact same skills at speed: deploy, scale, roll
|
||||
forward, add a liveness probe, edit a live object, and reason about Jobs/CronJobs/DaemonSets
|
||||
without a guided walkthrough alongside you.
|
||||
|
||||
## Why this exists
|
||||
|
||||
Real fluency with `kubectl` comes from repetition, not from reading about it a second time. Each
|
||||
Lab here is a small, self-contained task — no long setup, no new concepts — so you can move
|
||||
quickly and build the kind of muscle memory that matters when you're troubleshooting a real
|
||||
cluster under time pressure later in this program.
|
||||
|
||||
## How to use it
|
||||
|
||||
Work through the eight Labs in order. If one takes you more than a few minutes, that's useful
|
||||
information — it usually means the earlier module's concept hasn't fully clicked yet, and it's
|
||||
worth a quick re-read of that lesson before continuing rather than pushing through by trial and
|
||||
error.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Delivery:** This module has no lesson/demonstration/knowledge-check scaffolding by design — it
|
||||
is the original Phase 1-5 "workloads" scenario bank, reused here as a self-paced speed-practice
|
||||
set once the real teaching (root-06-m02/m03) has already landed. In an instructor-led cohort,
|
||||
this is a good candidate for a timed "workloads sprint" between the teaching modules and Jobs.
|
||||
|
||||
**Capstone note:** These eight scenarios are also the exact set the pre-existing
|
||||
`rootnode-path.json` "workloads" stage requires to unlock the Capstone assessment (a separate,
|
||||
older mechanism from this course's own prerequisite chain — see
|
||||
docs/ROOT_V1_CURRICULUM_MANIFEST.md). Do not mark this module optional/is_required=false without
|
||||
re-checking that gate; a student can only be told to skip it if the Capstone gate itself is
|
||||
intentionally changed too.
|
||||
|
||||
**Common mistake to watch for:** students treating the MCQ-style activities
|
||||
(jobs-cronjobs-mcq, daemonset-mcq, deployment-strategy-mcq) as throwaway trivia — they're
|
||||
checking the same conceptual judgment calls the reflection in root-06-m03 raised, just faster.
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
# Practice Bank: Services & Networking
|
||||
|
||||
Like the workloads practice bank before it, this module has no new lesson. **Services & Service
|
||||
Discovery** and **Ingress & NetworkPolicy Foundations** already taught the concepts. This is four
|
||||
short Labs that drill exposing a Deployment, reasoning about a NodePort, and resolving a Service
|
||||
by DNS from inside the cluster — the everyday networking moves you'll use constantly from here on.
|
||||
|
||||
## How to use it
|
||||
|
||||
Work through the four Labs in order. Two of them (`services-mcq`, `expose-service-basics`) will
|
||||
feel familiar — you did a close variant of `expose-service-basics` in the earlier teaching
|
||||
module. That's intentional: it's the same skill under slightly different conditions, checking
|
||||
that it generalizes rather than that you memorized one exact command.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Delivery:** No lesson/demonstration/knowledge-check scaffolding by design — this is the
|
||||
original Phase 1-5 "networking" scenario bank plus two extra Labs (`nodeport-task`,
|
||||
`dns-resolution-mcq`) not part of that original set, reused here as self-paced speed practice.
|
||||
|
||||
**Capstone note:** `services-mcq` and `expose-service-basics` are the exact pair the
|
||||
pre-existing `rootnode-path.json` "networking" stage requires to unlock the Capstone assessment
|
||||
— a separate, older mechanism from this course's own prerequisite chain (see
|
||||
docs/ROOT_V1_CURRICULUM_MANIFEST.md). Do not mark this module optional/is_required=false without
|
||||
re-checking that gate.
|
||||
|
||||
**Common mistake to watch for:** confusing a NodePort's cluster-internal ClusterIP with the
|
||||
node-level port it also opens — the DNS-resolution Lab is a good moment to have a student narrate
|
||||
out loud which address they expect to work from where.
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
# Practice Bank: General Troubleshooting Operations
|
||||
|
||||
**Kubernetes Health Probes & Troubleshooting** and **The RootNode Troubleshooting Framework**
|
||||
already taught you how to *think* about a broken cluster. This module is six short Labs that
|
||||
drill the individual `kubectl` operations you'll reach for while doing that thinking: pulling
|
||||
logs, copying files in and out of a container, running a one-off exec, cordoning a node, and
|
||||
reasoning about pod-to-node placement and log output.
|
||||
|
||||
## How to use it
|
||||
|
||||
Work through the six Labs in order. None of them require the full Observe/Evidence/Hypothesis/
|
||||
Test/Repair/Validate/Prevent framework from the previous module — they're deliberately narrower,
|
||||
so you can build raw command fluency without also holding the whole framework in your head at
|
||||
the same time.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Delivery:** No lesson/demonstration/knowledge-check scaffolding by design — this is the
|
||||
original Phase 1-5 "troubleshooting" scenario bank plus one extra Lab (`container-logging-mcq`)
|
||||
not part of that original set, reused here as self-paced speed practice.
|
||||
|
||||
**Capstone note:** The five original Labs (`extract-logs-basics`, `kubectl-cp-basics`,
|
||||
`exec-command-basics`, `node-cordon-basics`, `pod-node-assignment-mcq`) are the exact set the
|
||||
pre-existing `rootnode-path.json` "troubleshooting" stage requires to unlock the Capstone
|
||||
assessment — a separate, older mechanism from this course's own prerequisite chain (see
|
||||
docs/ROOT_V1_CURRICULUM_MANIFEST.md). Do not mark this module optional/is_required=false without
|
||||
re-checking that gate.
|
||||
|
||||
**Common mistake to watch for:** running `kubectl cp` with the source/destination arguments
|
||||
reversed — a good moment to have a student read the command back out loud before running it.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user