Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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