Compare commits
65
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6a5902ddf | ||
|
|
bfae2aafe0 | ||
|
|
74033b4cb8 | ||
|
|
0b2303a3e2 | ||
|
|
a4c0058f8c | ||
|
|
fbf52d8334 | ||
|
|
c86b3de611 | ||
|
|
77fd1869db | ||
|
|
f0ff14af38 | ||
|
|
80a5f6e457 | ||
|
|
281d60bc2d | ||
|
|
8b3e56e402 | ||
|
|
f0dde9b84a | ||
|
|
16622bd9ab | ||
|
|
b0e07bb17f | ||
|
|
788fb197e0 | ||
|
|
aa63d09fb8 | ||
|
|
40362ec0c9 | ||
|
|
24eee80354 | ||
|
|
df5e39f767 | ||
|
|
7d870d8e2b | ||
|
|
3510979196 | ||
|
|
d066410451 | ||
|
|
bd55b74b4f | ||
|
|
2864fe03dc | ||
|
|
f695b7c95d | ||
|
|
e48710a0be | ||
|
|
87ad1251ce | ||
|
|
59edceea1f | ||
|
|
a09b7aaa53 | ||
|
|
682ef2de47 | ||
|
|
e68b6f445a | ||
|
|
e007bcf000 | ||
|
|
b6516a74db | ||
|
|
f5379afd5c | ||
|
|
15b616274b | ||
|
|
87526bcf7f | ||
|
|
3c815cfeea | ||
|
|
bd0a10fbfb | ||
|
|
01d3764138 | ||
|
|
903b0ead43 | ||
|
|
7f6e1857c0 | ||
|
|
2db136875f | ||
|
|
505783e5f7 | ||
|
|
e0f954e714 | ||
|
|
54b35e8ad7 | ||
|
|
de6c5b3286 | ||
|
|
831486d664 | ||
|
|
960a638ec9 | ||
|
|
73cea1ba1d | ||
|
|
dbcc210889 | ||
|
|
86ea035e9b | ||
|
|
25409d3bd7 | ||
|
|
046a0fba18 | ||
|
|
bee57d89fd | ||
|
|
82f17a05e8 | ||
|
|
fd38d06055 | ||
|
|
8ffc923a84 | ||
|
|
db927b8081 | ||
|
|
8c09ef8f3e | ||
|
|
28c925069b | ||
|
|
e2298e95de | ||
|
|
fe702754cd | ||
|
|
f152b9a489 | ||
|
|
e4cbcf6c40 |
+19
@@ -42,6 +42,25 @@ RUN set -eux && \
|
||||
ln -sf /usr/local/bin/k3s /usr/local/bin/kubectl && \
|
||||
ln -sf /usr/local/bin/k3s /usr/local/bin/crictl
|
||||
|
||||
# ── Docker Engine (ROOT 04) + DNS tooling (ROOT 02) ──────────────────────────
|
||||
# A second, independent container runtime nested inside this already-Privileged Lab container
|
||||
# (see docs/ROOT_FOUNDATIONS_LAB_STRATEGY.md §3 for the security/resource evaluation and the
|
||||
# real test results this decision is based on) — its own dockerd, its own
|
||||
# /var/run/docker.sock, entirely separate from the Lab Host's real Docker socket, which is
|
||||
# never mounted into this image. docker-compose v2 isn't bundled by Ubuntu's docker.io package,
|
||||
# so fetch the static binary the same way k3s's own binary is fetched above, rather than adding
|
||||
# a whole new apt repository for one plugin.
|
||||
RUN apt-get update && apt-get install -y docker.io dnsutils && rm -rf /var/lib/apt/lists/*
|
||||
RUN set -eux && \
|
||||
case "$(uname -m)" in \
|
||||
x86_64) COMPOSE_ARCH="x86_64" ;; \
|
||||
aarch64) COMPOSE_ARCH="aarch64" ;; \
|
||||
*) echo "Unsupported arch: $(uname -m)" && exit 1 ;; \
|
||||
esac && \
|
||||
mkdir -p /usr/local/lib/docker/cli-plugins && \
|
||||
curl -fsSL "https://github.com/docker/compose/releases/latest/download/docker-compose-linux-${COMPOSE_ARCH}" \
|
||||
-o /usr/local/lib/docker/cli-plugins/docker-compose && \
|
||||
chmod +x /usr/local/lib/docker/cli-plugins/docker-compose
|
||||
|
||||
# ── App files ─────────────────────────────────────────────────────────────────
|
||||
WORKDIR /app
|
||||
|
||||
+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.
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
# How Computers Run Software
|
||||
|
||||
## Hardware, OS, and application: three different layers
|
||||
|
||||
When you run a program, three distinct layers cooperate:
|
||||
|
||||
- **Hardware** — the physical CPU, RAM, and storage. It has no idea what a "file" or a "process"
|
||||
is; it just executes instructions and stores bits.
|
||||
- **The operating system (kernel)** — the layer that turns raw hardware into something usable:
|
||||
it decides which program gets the CPU next, hands out memory, and organizes storage into
|
||||
files and directories. Linux is a kernel plus a large set of standard tools around it.
|
||||
- **Applications (userspace)** — the programs you actually run. They ask the kernel for
|
||||
resources (memory, files, network) through a well-defined interface (system calls) instead of
|
||||
touching hardware directly.
|
||||
|
||||
This module gives you just enough of this model to reason about what you're actually looking at
|
||||
for the rest of ROOT_ — not a full computer architecture course.
|
||||
|
||||
## CPU: the thing that actually executes instructions
|
||||
|
||||
A CPU has some number of **cores** — each core can run one stream of instructions at a time.
|
||||
`nproc` tells you how many cores/threads Linux believes it has available. When a system feels
|
||||
"slow," it's very often because more processes want CPU time than there are cores to give them.
|
||||
|
||||
## RAM: fast, temporary, and finite
|
||||
|
||||
RAM holds the data and code a running program is actively using. It's fast but **volatile** —
|
||||
its contents disappear when power is lost, which is exactly why persistent data lives on
|
||||
storage instead. `free -h` shows you total memory, how much is in use, and how much is free.
|
||||
|
||||
## Storage: slower, but persistent
|
||||
|
||||
Storage (an SSD/disk) keeps data even when the machine is off. It's organized into a
|
||||
**filesystem** — the tree of directories and files you already have some intuition for. `df -h`
|
||||
shows how much space exists and how much is used on each mounted filesystem.
|
||||
|
||||
## Processes: a running program
|
||||
|
||||
A **process** is a program actually being executed — code plus its own private memory, tracked
|
||||
by the kernel with a unique ID (a PID). One program can have many processes running at once
|
||||
(open two terminals running the same shell, and you have two separate processes). ROOT 01
|
||||
Module 5 goes much deeper into this; for now, just know: everything you run becomes a process
|
||||
the kernel is responsible for scheduling onto a CPU core.
|
||||
|
||||
## Why this matters for everything after this module
|
||||
|
||||
Every concept the rest of ROOT_ builds on is a refinement of this same idea. A container is
|
||||
(mostly) an isolated process with its own filesystem view. A Kubernetes Pod is a group of
|
||||
containers the scheduler places onto a specific machine's CPU/RAM. None of that will make sense
|
||||
as anything other than magic if you don't first have this base model of what a computer is
|
||||
actually managing.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Lesson goal:** Give students just enough of a mental model to stop treating "the computer" as
|
||||
an opaque box — without turning this into a full CS/architecture course.
|
||||
|
||||
**Approximate timing:** 15 minutes read; 10 minutes live with discussion.
|
||||
|
||||
**Common misconception to watch for:** students often conflate RAM and storage ("my computer
|
||||
has 8GB" meaning disk space). Draw the volatile-vs-persistent distinction explicitly and early.
|
||||
|
||||
**Discussion question:** "Why can't a program just talk to the hardware directly instead of
|
||||
going through the kernel?" (Answer: the kernel is what lets many programs safely share one CPU/
|
||||
one block of RAM without stepping on each other — this is worth naming explicitly, as it's the
|
||||
root of why containers and Kubernetes are even possible.)
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
# Guided Exercise: Predict Before You Look
|
||||
|
||||
Before you run the real inspection commands in the Lab, commit to a guess — this is what makes
|
||||
the real numbers meaningful instead of just scrolling past them.
|
||||
|
||||
## Your task
|
||||
|
||||
On paper or in a scratch file, write down your best GUESS for:
|
||||
|
||||
1. How many CPU cores does this machine have?
|
||||
2. How much total RAM, roughly, in GB?
|
||||
3. How much free disk space is on the root filesystem?
|
||||
|
||||
Then think through: which of these three would you expect a "student Lab" environment to be
|
||||
MOST constrained on, and why? (Hint: think about what happens if 50 students are each running
|
||||
their own Lab on one shared physical machine at the same time.)
|
||||
|
||||
## A worked shape to check yourself against
|
||||
|
||||
There's no single right numeric answer here — the point is the habit of predicting first. A
|
||||
reasonable answer to the "most constrained" question: memory and CPU are typically the tightest
|
||||
shared resource on a multi-tenant host, since every additional Lab needs its own slice of both,
|
||||
while disk is comparatively cheap to over-provision.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Have students commit to a number verbally or on a shared doc BEFORE anyone
|
||||
runs the real commands — the value is in noticing the gap between guess and reality, not in
|
||||
getting it right.
|
||||
|
||||
**Approximate timing:** 5 minutes.
|
||||
|
||||
**Optional extension:** ask what would happen to their guess if this were a "real" laptop
|
||||
they personally own vs. a shared cloud server — good primer for later resource-limit
|
||||
discussions (ROOT 06's resource requests/limits, ROOT 09's resource governance).
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
# Reflection: You Just Looked Inside the Box
|
||||
|
||||
## Reflection questions
|
||||
|
||||
1. How close was your guess to the real numbers? What surprised you most?
|
||||
2. Name one thing you now understand about "the computer" that you couldn't have explained
|
||||
before this module.
|
||||
3. When you eventually run `kubectl describe pod` and see `requests`/`limits` for CPU and
|
||||
memory, what do you now expect those numbers to actually be constraining?
|
||||
|
||||
## Real Ops takeaway
|
||||
|
||||
**Every resource limit you'll ever configure — in Docker, in Kubernetes, in a systemd unit — is
|
||||
a promise about exactly the three things this module named: CPU, memory, and storage.** You
|
||||
can't reason well about "why did my container get killed" or "why is my Pod stuck Pending"
|
||||
without this base model. This module isn't computer-science trivia — it's the vocabulary the
|
||||
rest of ROOT_ assumes you have.
|
||||
|
||||
This module is complete. Continue to the next module.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Discussion questions for a live classroom:** question 3 is worth spending real time on — it's
|
||||
the first moment students connect this abstract lesson to something they'll type as a real
|
||||
command many courses from now. Naming that connection explicitly here pays off later.
|
||||
|
||||
**Approximate timing:** 5-8 minutes live discussion; 5 minutes self-paced writing.
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
# Linux and the Shell
|
||||
|
||||
## What "Linux" actually means
|
||||
|
||||
Linux is a **kernel** — the core piece of software from Module 1 that manages CPU, memory, and
|
||||
storage. What most people call "a Linux system" (Ubuntu, Debian, Fedora, Alpine...) is a
|
||||
**distribution**: the Linux kernel plus a chosen set of standard tools, package manager, and
|
||||
configuration conventions layered on top. Different distributions, same kernel underneath —
|
||||
which is why a command you learn on Ubuntu mostly still works on Alpine.
|
||||
|
||||
## Shell vs. terminal
|
||||
|
||||
These get used interchangeably but are different things:
|
||||
|
||||
- The **terminal** is the window/program that displays text and sends your keystrokes somewhere.
|
||||
- The **shell** is the program actually interpreting what you type as commands — `bash` is the
|
||||
shell this Lab uses. The terminal is just the display; the shell is the brain.
|
||||
|
||||
## Command structure
|
||||
|
||||
Every command follows roughly the same shape:
|
||||
|
||||
```
|
||||
command [options] [arguments]
|
||||
```
|
||||
|
||||
`ls -la /var/log` — `ls` is the command, `-la` are options (flags) that change its behavior,
|
||||
`/var/log` is the argument (what to act on). Options can usually be combined (`-l -a` = `-la`).
|
||||
|
||||
## Exit codes: how a command reports success or failure
|
||||
|
||||
Every command returns a number when it finishes: `0` means success, anything else (1-255) means
|
||||
some kind of failure. This isn't a Linux quirk — it's the exact mechanism scripts, CI pipelines,
|
||||
and (much later) Kubernetes health checks all rely on to know whether something worked. You can
|
||||
read the last command's exit code with the special variable `$?`.
|
||||
|
||||
## Getting help without leaving the terminal
|
||||
|
||||
- **Tab completion** — press Tab while typing a command or path; the shell completes it for you
|
||||
or shows you the options. This is a genuine time-saver, not just a convenience.
|
||||
- **Command history** — press the Up arrow to recall previous commands; `history` lists them.
|
||||
- **`man <command>`** — the manual page for almost any command, with every option explained.
|
||||
`man ls`, `man grep`, `man chmod` are all real, useful references — reaching for `man` before
|
||||
a search engine is a habit worth building now.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Lesson goal:** Establish shell fluency vocabulary (shell vs. terminal, exit codes, options vs.
|
||||
arguments) that every later module assumes without re-explaining.
|
||||
|
||||
**Approximate timing:** 15 minutes read; 10 minutes live with a short live demo of tab
|
||||
completion and `man`.
|
||||
|
||||
**Common misconception to watch for:** students often think "Linux" and "the terminal" are the
|
||||
same thing — clarify the kernel/distribution/shell/terminal layering explicitly.
|
||||
|
||||
**Demo idea:** live-type a command that fails (e.g. `ls /nonexistent`), then run `echo $?`
|
||||
immediately after to show the exit code in real time — more convincing than describing it.
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
# Demonstration: Watching Exit Codes in Real Time
|
||||
|
||||
## OBSERVE
|
||||
|
||||
Run a command you know will succeed, then immediately check its exit code:
|
||||
|
||||
```
|
||||
$ true
|
||||
$ echo $?
|
||||
0
|
||||
```
|
||||
|
||||
## HYPOTHESIS
|
||||
|
||||
Now predict: what will the exit code be if the command fails instead?
|
||||
|
||||
## INSPECT
|
||||
|
||||
Run a command that fails, and check immediately:
|
||||
|
||||
```
|
||||
$ ls /this-does-not-exist
|
||||
ls: cannot access '/this-does-not-exist': No such file or directory
|
||||
$ echo $?
|
||||
2
|
||||
```
|
||||
|
||||
## CHANGE
|
||||
|
||||
Try a few other failing commands and note their codes: `grep nomatch /etc/hostname` (exit 1 —
|
||||
"no matches found," a different kind of failure than "file not found").
|
||||
|
||||
## VERIFY
|
||||
|
||||
Notice: exit codes aren't just "0 or 1" — different failures often get different non-zero
|
||||
codes. `0` always means success; the exact non-zero value's MEANING varies by command, but ANY
|
||||
non-zero value reliably means "something went wrong," which is the part every script actually
|
||||
relies on (`if command; then ... else ...`).
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Demo goal:** Make the abstract "exit code" concept concrete by actually watching `$?` change
|
||||
in real time, immediately after each command — this is far more convincing live than described
|
||||
in prose.
|
||||
|
||||
**Approximate timing:** 8 minutes live.
|
||||
|
||||
**Common mistake to watch for:** running `echo $?` after ANY other command in between (even
|
||||
`ls`) silently checks the WRONG command's exit code — emphasize "check it immediately."
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
# Knowledge Check: Linux and the Shell
|
||||
|
||||
---
|
||||
|
||||
**1. What's the difference between a shell and a terminal?**
|
||||
|
||||
*Answer:* The terminal is the window/program that displays text and captures keystrokes; the
|
||||
shell (e.g. `bash`) is the program actually interpreting what you type as commands. The terminal
|
||||
is the display, the shell is the brain.
|
||||
|
||||
---
|
||||
|
||||
**2. A command exits with code `0`. What does that tell you?**
|
||||
|
||||
*Answer:* It succeeded. `0` is the universal "success" convention every script and pipeline
|
||||
relies on; any non-zero value means some kind of failure occurred.
|
||||
|
||||
---
|
||||
|
||||
**3. You've typed half a long path and can't remember the rest. What should you press?**
|
||||
|
||||
*Answer:* Tab — tab completion finishes the path (or shows the options) without you needing to
|
||||
type or remember it exactly.
|
||||
|
||||
---
|
||||
|
||||
**4. Where would you look to learn every option a command supports, without leaving the
|
||||
terminal?**
|
||||
|
||||
*Answer:* Its manual page — `man <command>`.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Question 1 is the one students most often blur together — worth confirming
|
||||
verbally before moving on.
|
||||
|
||||
**Approximate timing:** 5 minutes.
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
# Guided Exercise: Predict the Exit Code Before You Run It
|
||||
|
||||
This is a paper exercise — commit to an answer before checking it in the Lab.
|
||||
|
||||
## Your task
|
||||
|
||||
For each command below, predict whether its exit code will be `0` (success) or non-zero
|
||||
(failure), and why:
|
||||
|
||||
1. `cat /etc/hostname` (a file that exists)
|
||||
2. `cat /etc/does-not-exist-at-all` (a file that doesn't)
|
||||
3. `grep root /etc/passwd` (a pattern that's very likely present)
|
||||
4. `grep zzz-not-a-real-user /etc/passwd` (a pattern that's very likely absent)
|
||||
5. `mkdir /tmp/already-exists && mkdir /tmp/already-exists` (running the same `mkdir` twice)
|
||||
|
||||
## A worked shape to check yourself against
|
||||
|
||||
1. `0` — the file exists and is readable.
|
||||
2. Non-zero — the file doesn't exist, `cat` fails.
|
||||
3. `0` — `grep` succeeds (exit 0) when it finds at least one match.
|
||||
4. Non-zero (specifically `1`) — `grep` "fails" when it finds NO matches, even though nothing
|
||||
went technically wrong. This is a common surprise: `grep`'s exit code means "did I find a
|
||||
match," not "did an error occur."
|
||||
5. The SECOND `mkdir` fails (non-zero) — the directory already exists. The first one succeeds.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Item 4 is the one worth pausing on — many students assume "no error message"
|
||||
means "exit code 0," and `grep`'s no-match convention is a good early counter-example.
|
||||
|
||||
**Approximate timing:** 8-10 minutes live discussion; 5 minutes self-paced.
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
# Reflection: Exit Codes Are Everywhere After This
|
||||
|
||||
## Reflection questions
|
||||
|
||||
1. Before this module, did you know commands returned a machine-readable success/failure
|
||||
signal at all? What did you assume happened instead?
|
||||
2. Where do you think exit codes might matter later in ROOT_ — in a script, a CI pipeline, or
|
||||
a container?
|
||||
|
||||
## Real Ops takeaway
|
||||
|
||||
**Every automated system you'll ever build or operate checks exit codes, not error messages.**
|
||||
A script deciding whether to continue, a CI pipeline deciding whether to deploy, and (much
|
||||
later) a container orchestrator deciding whether a process is healthy, all reduce to the exact
|
||||
same question this module taught you to answer by hand: did this exit `0` or not? Getting
|
||||
comfortable checking `$?` now means none of those later systems will feel like magic.
|
||||
|
||||
This module is complete. Continue to the next module.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Discussion questions for a live classroom:** question 2 previews Docker/Kubernetes health
|
||||
checks well before students get there — useful to name explicitly without going deep yet.
|
||||
|
||||
**Approximate timing:** 5-8 minutes live discussion; 5 minutes self-paced writing.
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
# Navigating the Filesystem
|
||||
|
||||
## Everything is one tree, starting at `/`
|
||||
|
||||
Linux has exactly one filesystem tree, rooted at `/` (pronounced "root," unrelated to your
|
||||
username). Every file and directory, no matter what physical disk it's actually on, appears
|
||||
somewhere under this one tree — there's no separate "C:\" and "D:\" the way Windows shows
|
||||
multiple drives.
|
||||
|
||||
## Absolute vs. relative paths
|
||||
|
||||
An **absolute path** starts with `/` and always means the same location no matter where you
|
||||
currently are: `/home/student/notes.txt`. A **relative path** is interpreted relative to your
|
||||
CURRENT directory: `notes.txt` means something different depending on where you're standing
|
||||
when you type it. `.` means "this directory," `..` means "one directory up" — `cd ..` moves up
|
||||
one level, `cd ../..` moves up two.
|
||||
|
||||
## Key commands
|
||||
|
||||
- `pwd` — print your current directory (where am I?)
|
||||
- `ls` — list what's in a directory (`ls -la` shows hidden files and details)
|
||||
- `cd <path>` — change directory (absolute or relative)
|
||||
|
||||
## Hidden files
|
||||
|
||||
A file or directory starting with `.` (like `.bashrc` or `.config`) is "hidden" — `ls` skips it
|
||||
by default, `ls -a` shows it. This isn't a security feature, just a convention to keep
|
||||
configuration/metadata files out of the way of normal directory listings.
|
||||
|
||||
## The important top-level directories (conceptually, not memorized)
|
||||
|
||||
You don't need to memorize the entire Filesystem Hierarchy Standard, but recognizing these by
|
||||
purpose will save you real confusion later:
|
||||
|
||||
- **`/etc`** — system-wide configuration files (nginx's config lives at `/etc/nginx/nginx.conf`)
|
||||
- **`/var`** — variable data that changes while the system runs: logs (`/var/log`), among others
|
||||
- **`/home`** — personal directories for regular users
|
||||
- **`/tmp`** — temporary files, safe to assume anything here can disappear on reboot
|
||||
- **`/usr`** — most installed programs and their supporting files live somewhere under here
|
||||
- **`/proc`** — not real files on disk at all — a live, kernel-generated view into running
|
||||
processes and system state (you'll use this again in Module 5)
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Lesson goal:** Give students a working mental model of "one tree, absolute vs. relative
|
||||
paths" plus just enough top-level-directory recognition to not be lost later — not FHS
|
||||
memorization.
|
||||
|
||||
**Approximate timing:** 15 minutes read; 10 minutes live.
|
||||
|
||||
**Common misconception to watch for:** students coming from Windows sometimes expect separate
|
||||
drives/roots — emphasize the single unified tree explicitly.
|
||||
|
||||
**Demo idea:** `cd /` then `ls`, narrating what each top-level directory is for as you see it,
|
||||
rather than listing them in the abstract.
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
# Demonstration: The Same Word, Two Different Destinations
|
||||
|
||||
## OBSERVE
|
||||
|
||||
Starting from `/home/student`, run `cd project` — you land at `/home/student/project`.
|
||||
|
||||
## HYPOTHESIS
|
||||
|
||||
Now, from `/home/student/project`, what do you predict `cd project` will do this time?
|
||||
|
||||
## INSPECT
|
||||
|
||||
```
|
||||
$ pwd
|
||||
/home/student/project
|
||||
$ cd project
|
||||
bash: cd: project: No such file or directory
|
||||
```
|
||||
|
||||
It fails — there's no `project` directory INSIDE `project` (unless you happened to create one).
|
||||
The exact same relative path meant something completely different depending on where you stood.
|
||||
|
||||
## CHANGE
|
||||
|
||||
From `/home/student/project`, run `cd ../project` instead — this correctly goes back up one
|
||||
level, then into `project` again, landing you right back where you started.
|
||||
|
||||
## VERIFY
|
||||
|
||||
`pwd` after each `cd` to confirm exactly where you ended up — this habit (verify your location
|
||||
before assuming it) prevents a huge share of "why did my command run somewhere unexpected"
|
||||
confusion later.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Demo goal:** Make "relative paths depend on where you are" concrete and slightly surprising,
|
||||
rather than an abstract rule to memorize.
|
||||
|
||||
**Approximate timing:** 8 minutes live.
|
||||
|
||||
**Common mistake to watch for:** students assume a relative path always means the SAME thing
|
||||
once they've used it once — reinforce that it's re-evaluated fresh every time, from wherever
|
||||
they currently are.
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
# Knowledge Check: Navigating the Filesystem
|
||||
|
||||
---
|
||||
|
||||
**1. What does `..` mean in a path?**
|
||||
|
||||
*Answer:* One directory up from your current location.
|
||||
|
||||
---
|
||||
|
||||
**2. You're at `/home/student/project`. Does `cd project` do the same thing here as it did at
|
||||
`/home/student`?**
|
||||
|
||||
*Answer:* No — a relative path is re-evaluated from your CURRENT location every time. At
|
||||
`/home/student`, `project` refers to `/home/student/project`; from inside that same directory,
|
||||
`project` would refer to `/home/student/project/project`, which likely doesn't exist.
|
||||
|
||||
---
|
||||
|
||||
**3. A file is "hidden" from a normal `ls`. What actually makes it hidden?**
|
||||
|
||||
*Answer:* Its name starts with a `.` — it's purely a naming convention `ls` respects by
|
||||
default, not a real permission or security feature. `ls -a` shows it.
|
||||
|
||||
---
|
||||
|
||||
**4. Where would you expect to find a running system's log files?**
|
||||
|
||||
*Answer:* `/var/log` — `/var` holds data that changes while the system runs.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Question 2 is the one worth confirming out loud — it's the core "gotcha" of
|
||||
relative paths.
|
||||
|
||||
**Approximate timing:** 5 minutes.
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
# Guided Exercise: Plan a Directory Structure on Paper First
|
||||
|
||||
Before you build anything in the Lab, plan it — this is the same discipline you'll use later
|
||||
when planning a Kubernetes namespace layout or a Git repository structure.
|
||||
|
||||
## Your task
|
||||
|
||||
You're setting up a small project called `weather-app`. On paper, sketch a directory tree
|
||||
containing:
|
||||
|
||||
- A place for source code
|
||||
- A place for documentation
|
||||
- A place for tests
|
||||
- A single hidden configuration file at the project's root
|
||||
|
||||
Write out the exact `mkdir` and file-creation commands you'd use, including whether each path
|
||||
you write is absolute or relative.
|
||||
|
||||
## A worked shape to check yourself against
|
||||
|
||||
```
|
||||
mkdir -p weather-app/src weather-app/docs weather-app/tests
|
||||
echo "ready" > weather-app/.config
|
||||
```
|
||||
|
||||
(Or with an absolute path: `mkdir -p /srv/weather-app/src ...`.) The `-p` flag matters here —
|
||||
without it, `mkdir` refuses to create `weather-app/src` if `weather-app` doesn't already exist.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Have students commit to their command in writing before revealing the worked
|
||||
answer — the value is in noticing what `-p` actually buys them, not just copying a correct
|
||||
command.
|
||||
|
||||
**Approximate timing:** 8-10 minutes live discussion; 5 minutes self-paced.
|
||||
|
||||
**Common mistake to watch for:** forgetting `mkdir -p` and getting a "No such file or
|
||||
directory" error when creating a nested path in one command — a good moment to connect back to
|
||||
Module 2's exit-code lesson (that error IS a non-zero exit code).
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
# Reflection: Structure Isn't Bureaucracy, It's Communication
|
||||
|
||||
## Reflection questions
|
||||
|
||||
1. Why might a real engineering team insist on a consistent directory structure across every
|
||||
project, instead of everyone organizing files however they like?
|
||||
2. Which of `/etc`, `/var`, `/home`, `/tmp`, `/usr`, `/proc` do you feel most confident you could
|
||||
explain to someone else right now? Which one still feels fuzzy?
|
||||
|
||||
## Real Ops takeaway
|
||||
|
||||
**A predictable filesystem layout is what lets tools (and other engineers) reason about a
|
||||
system without asking you first.** `/etc` always means configuration, `/var/log` always means
|
||||
logs — this convention is exactly why a monitoring tool can be pointed at "check `/var/log`" on
|
||||
any Linux machine and just work. Container images later inherit this same convention (an
|
||||
nginx container still keeps its config at `/etc/nginx/nginx.conf`) — you're not learning a
|
||||
one-off Linux fact, you're learning a convention that outlives this specific module.
|
||||
|
||||
This module is complete. Continue to the next module.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Discussion questions for a live classroom:** question 1 connects directly to why Kubernetes
|
||||
namespaces and Git repository conventions matter later — a good moment to name that connection
|
||||
without going deep yet.
|
||||
|
||||
**Approximate timing:** 5-8 minutes live discussion; 5 minutes self-paced writing.
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
# Files and Text
|
||||
|
||||
## Basic file operations
|
||||
|
||||
- `touch <file>` — create an empty file (or update its timestamp if it exists)
|
||||
- `mkdir <dir>` — create a directory (`-p` creates any missing parent directories too)
|
||||
- `cp <src> <dst>` — copy a file
|
||||
- `mv <src> <dst>` — move OR rename a file (Linux treats renaming as "moving to a new name in
|
||||
the same place")
|
||||
- `rm <file>` — delete a file (`-r` for a directory, `-f` to skip confirmation — dangerous
|
||||
combined: `rm -rf` deletes without asking, permanently)
|
||||
|
||||
## Reading text
|
||||
|
||||
- `cat <file>` — print the whole file at once
|
||||
- `less <file>` — page through a file interactively (press `q` to quit) — the right tool for
|
||||
anything too long to read in one screen
|
||||
- `head <file>` / `tail <file>` — the first/last 10 lines by default (`-n 20` for a different
|
||||
count); `tail -f` follows a file live as new lines are appended — exactly how you'd watch a
|
||||
log file update in real time
|
||||
|
||||
## Searching text: `grep`
|
||||
|
||||
`grep PATTERN file` prints only the lines matching PATTERN. This is one of the most-used
|
||||
commands in real operations work — "find the lines that matter" beats "read everything."
|
||||
|
||||
## Pipes and redirection: composing commands
|
||||
|
||||
- **A pipe (`|`)** sends one command's output as the next command's input:
|
||||
`cat app.log | grep ERROR` — read the file, then filter it.
|
||||
- **Redirection (`>`)** sends a command's output into a file instead of the screen (overwriting
|
||||
it): `grep ERROR app.log > errors.txt`. `>>` appends instead of overwriting.
|
||||
|
||||
## stdout vs. stderr
|
||||
|
||||
A command actually has TWO separate output streams: **stdout** (normal output) and **stderr**
|
||||
(error messages), even though both print to your screen by default. This is why
|
||||
`command 2>/dev/null` (redirecting stream `2`, stderr, to nowhere) can suppress error noise
|
||||
while still showing normal output — you'll use exactly this pattern in later Labs.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Lesson goal:** Build real fluency composing small commands together — this is the single
|
||||
highest-leverage Linux skill for troubleshooting work later in ROOT_.
|
||||
|
||||
**Approximate timing:** 15 minutes read; 10 minutes live.
|
||||
|
||||
**Common misconception to watch for:** students often don't realize stdout/stderr are separate
|
||||
streams until they see `2>/dev/null` change what's suppressed — worth demonstrating live.
|
||||
|
||||
**Demo idea:** run a command that produces both real output and an error in the same call
|
||||
(e.g. `ls /etc /nonexistent`) and show how `2>/dev/null` changes what appears.
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
# Demonstration: Filtering a Log Down to What Matters
|
||||
|
||||
## OBSERVE
|
||||
|
||||
A raw log file has five lines mixing `INFO` and `ERROR`:
|
||||
|
||||
```
|
||||
$ cat app.log
|
||||
INFO: boot ok
|
||||
ERROR: disk full
|
||||
INFO: retry
|
||||
ERROR: timeout
|
||||
INFO: done
|
||||
```
|
||||
|
||||
## HYPOTHESIS
|
||||
|
||||
Predict what `grep ERROR app.log` will print, before running it.
|
||||
|
||||
## INSPECT
|
||||
|
||||
```
|
||||
$ grep ERROR app.log
|
||||
ERROR: disk full
|
||||
ERROR: timeout
|
||||
```
|
||||
|
||||
Only the matching lines — everything else is filtered out.
|
||||
|
||||
## CHANGE
|
||||
|
||||
Now redirect that filtered result into a new file instead of just viewing it:
|
||||
|
||||
```
|
||||
$ grep ERROR app.log > errors.txt
|
||||
$ cat errors.txt
|
||||
ERROR: disk full
|
||||
ERROR: timeout
|
||||
```
|
||||
|
||||
## VERIFY
|
||||
|
||||
Count how many lines actually matched, without eyeballing them: `wc -l < errors.txt` (or
|
||||
`grep -c ERROR app.log` directly). This composition — filter, then count, then act — is the
|
||||
exact shape of a huge share of real troubleshooting work.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Demo goal:** Show the filter → redirect → verify chain as one continuous, composable
|
||||
workflow, not three unrelated commands.
|
||||
|
||||
**Approximate timing:** 8 minutes live.
|
||||
|
||||
**Common mistake to watch for:** using `>` when they meant `>>` and accidentally overwriting a
|
||||
file they wanted to append to — worth calling out explicitly since it's a real, common mistake
|
||||
with no undo.
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
# Knowledge Check: Files and Text
|
||||
|
||||
---
|
||||
|
||||
**1. What's the difference between `>` and `>>`?**
|
||||
|
||||
*Answer:* `>` overwrites the target file's contents entirely; `>>` appends to the end of it,
|
||||
preserving what was already there.
|
||||
|
||||
---
|
||||
|
||||
**2. A command prints both normal output and an error message. What are these two streams
|
||||
called?**
|
||||
|
||||
*Answer:* stdout (normal output) and stderr (error messages) — two separate streams that both
|
||||
happen to print to your screen by default.
|
||||
|
||||
---
|
||||
|
||||
**3. You want to watch a log file update live, as new lines are appended, without re-running a
|
||||
command repeatedly.**
|
||||
|
||||
*Answer:* `tail -f <file>` — follows the file and prints new lines as they're written.
|
||||
|
||||
---
|
||||
|
||||
**4. `cat app.log | grep ERROR`. What does the `|` actually do?**
|
||||
|
||||
*Answer:* It pipes `cat`'s output directly into `grep`'s input, so `grep` filters the file's
|
||||
contents without needing a separate temporary file.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Question 2 is worth a live show-of-hands check — many students haven't
|
||||
consciously registered that stdout/stderr are separate until this module.
|
||||
|
||||
**Approximate timing:** 5 minutes.
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
# Guided Exercise: Which Tool Actually Answers the Question?
|
||||
|
||||
This is a paper exercise — commit to an answer before checking it in the Lab.
|
||||
|
||||
## Your task
|
||||
|
||||
For each situation, name the ONE command (or short pipeline) you'd reach for first:
|
||||
|
||||
1. You want to see just the last 20 lines of a 50,000-line log file.
|
||||
2. You want to know how many lines in a file contain the word "timeout."
|
||||
3. You want to watch a log file update live while a process is running.
|
||||
4. You want to see a file's full contents but it's too long for one screen and you want to
|
||||
scroll through it interactively.
|
||||
|
||||
## A worked shape to check yourself against
|
||||
|
||||
1. `tail -n 20 file.log`
|
||||
2. `grep -c timeout file.log`
|
||||
3. `tail -f file.log`
|
||||
4. `less file.log`
|
||||
|
||||
Notice: `cat` would technically "work" for #4 too, but it dumps everything at once with no way
|
||||
to scroll back — `less` is the actual right tool once a file is longer than one screen.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Item 4 is worth discussing explicitly — "it works" isn't the same bar as
|
||||
"it's the right tool for this situation," and that distinction matters more as commands compose
|
||||
into real troubleshooting workflows.
|
||||
|
||||
**Approximate timing:** 8-10 minutes live discussion; 5 minutes self-paced.
|
||||
@@ -0,0 +1,26 @@
|
||||
# Reflection: Filtering Is a Reasoning Skill, Not a Syntax Trick
|
||||
|
||||
## Reflection questions
|
||||
|
||||
1. Before this module, how would you have found "the important lines" in a huge log file?
|
||||
How does that compare to `grep`?
|
||||
2. Which combination (pipe, redirect, or a specific command like `tail -f`) do you expect to
|
||||
use the most once you're doing real troubleshooting work?
|
||||
|
||||
## Real Ops takeaway
|
||||
|
||||
**"Read everything and eyeball it" does not scale — filtering does.** Every troubleshooting
|
||||
framework you'll learn later in ROOT_ (starting with ROOT 02's own version, then again more
|
||||
formally in ROOT 10) depends on being able to quickly narrow a huge amount of system output down
|
||||
to the handful of lines that actually matter. `grep`, pipes, and redirection are the first real
|
||||
tools you have for that — not a syntax exercise, a reasoning skill.
|
||||
|
||||
This module is complete. Continue to the next module.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Discussion questions for a live classroom:** question 1 often produces good answers ("I'd
|
||||
just scroll and read") that make the value of filtering obvious by contrast — let students
|
||||
articulate this themselves rather than stating it for them.
|
||||
|
||||
**Approximate timing:** 5-8 minutes live discussion; 5 minutes self-paced writing.
|
||||
@@ -0,0 +1,64 @@
|
||||
# Processes
|
||||
|
||||
## What a process actually is
|
||||
|
||||
A **process** is a running instance of a program — its own code, its own private memory, and a
|
||||
unique identifier the kernel tracks called a **PID** (Process ID). Run the same program twice
|
||||
(open two terminals both running `bash`), and you get two separate processes with two different
|
||||
PIDs, even though they're running identical code.
|
||||
|
||||
## Foreground vs. background
|
||||
|
||||
A command you run normally occupies your terminal until it finishes (**foreground**) — you
|
||||
can't type anything else until it's done. Append `&` to run it in the **background** instead,
|
||||
freeing your terminal immediately while the process keeps running. `sleep 300 &` starts a
|
||||
300-second sleep in the background and hands your prompt right back.
|
||||
|
||||
## Inspecting processes: `ps` and `top`
|
||||
|
||||
- `ps aux` — a snapshot of every process currently running, including its PID, how much CPU/
|
||||
memory it's using, and the exact command that started it.
|
||||
- `top` (or `htop`, if installed) — the same idea, but live-updating and sorted by resource
|
||||
usage, useful for "what's actually eating CPU/memory right now."
|
||||
|
||||
## Stopping a process: `kill` and signals
|
||||
|
||||
`kill <PID>` doesn't instantly destroy a process — it sends it a **signal**, a message the
|
||||
process can (usually) choose how to respond to. The default signal, `SIGTERM` (15), politely
|
||||
asks a process to shut down, giving it a chance to clean up first. `kill -9 <PID>` sends
|
||||
`SIGKILL`, which the kernel enforces immediately with no chance for the process to react —
|
||||
appropriate only when a process is truly stuck and ignoring `SIGTERM`. `kill -0 <PID>` is a
|
||||
special case: it sends NO real signal at all, just checks whether the PID still exists —
|
||||
exactly how you confirm "did that process actually stop" without side effects.
|
||||
|
||||
## Process hierarchy
|
||||
|
||||
Every process (except the very first one at boot) has a **parent** — the process that started
|
||||
it. Your shell is the parent of every command you run from it. This matters later: a parent
|
||||
process's own lifecycle can affect its children (this is part of why a container, which is
|
||||
often just one process tree, behaves the way it does when its main process exits).
|
||||
|
||||
## Why this matters for containers and Kubernetes
|
||||
|
||||
A container is, underneath, mostly an isolated process (or small process tree) with its own
|
||||
view of the filesystem and network — when people say "a container exited," they mean its main
|
||||
process exited, exactly the same event you'll cause on purpose in this module's Lab. Kubernetes'
|
||||
entire job, at its core, is deciding which machine a process (inside a container) runs on and
|
||||
restarting it if it dies unexpectedly — none of that will feel intuitive without this module's
|
||||
model of what a process actually is.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Lesson goal:** Build a real process mental model (PID, foreground/background, signals,
|
||||
parent/child) that ROOT 04 (containers) and ROOT 06 (Kubernetes workloads) both directly build
|
||||
on without re-explaining.
|
||||
|
||||
**Approximate timing:** 15 minutes read; 10 minutes live.
|
||||
|
||||
**Common misconception to watch for:** students often think `kill` always means "forcefully
|
||||
destroy" — clarify that plain `kill` sends a polite, ignorable request (SIGTERM), and `-9` is
|
||||
the forceful escalation, not the default.
|
||||
|
||||
**Discussion question:** "What do you think happens to a container if its main process exits on
|
||||
its own, versus if something outside kills it?" (Don't need a complete answer yet — just plant
|
||||
the question for later.)
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
# Demonstration: Watching a Process Actually Terminate
|
||||
|
||||
## OBSERVE
|
||||
|
||||
Start a long-running background process and note its PID:
|
||||
|
||||
```
|
||||
$ sleep 300 &
|
||||
[1] 1421
|
||||
```
|
||||
|
||||
`1421` is the PID (yours will differ every time).
|
||||
|
||||
## HYPOTHESIS
|
||||
|
||||
Predict: will `kill -0 1421` print anything if the process is still running?
|
||||
|
||||
## INSPECT
|
||||
|
||||
```
|
||||
$ kill -0 1421 && echo "still running"
|
||||
still running
|
||||
```
|
||||
|
||||
`kill -0` sends no real signal — silence (or, here, our own `echo`) means the process exists.
|
||||
|
||||
## CHANGE
|
||||
|
||||
Now actually terminate it:
|
||||
|
||||
```
|
||||
$ kill 1421
|
||||
```
|
||||
|
||||
## VERIFY
|
||||
|
||||
```
|
||||
$ kill -0 1421 2>/dev/null && echo "still running" || echo "gone"
|
||||
gone
|
||||
```
|
||||
|
||||
The exact same check now reports the opposite result — real, observable proof the process is
|
||||
gone, not an assumption.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Demo goal:** Make "checking before and after" a visible habit, not just narrated — students
|
||||
should see the SAME command return a different real answer once state actually changed.
|
||||
|
||||
**Approximate timing:** 8 minutes live.
|
||||
|
||||
**Common mistake to watch for:** reaching for `kill -9` by default "to be safe" — reinforce that
|
||||
plain `kill` (SIGTERM) is the correct first attempt almost always.
|
||||
@@ -0,0 +1,38 @@
|
||||
# Knowledge Check: Processes
|
||||
|
||||
---
|
||||
|
||||
**1. What does a PID identify?**
|
||||
|
||||
*Answer:* One specific running instance of a program — the kernel's unique identifier for that
|
||||
process, distinct from every other running process, even other copies of the same program.
|
||||
|
||||
---
|
||||
|
||||
**2. What's the actual difference between `kill <pid>` and `kill -9 <pid>`?**
|
||||
|
||||
*Answer:* Plain `kill` sends SIGTERM, a polite request the process can choose to handle (clean
|
||||
up, then exit). `kill -9` sends SIGKILL, which the kernel enforces immediately with no chance
|
||||
for the process to react — an escalation for when SIGTERM is ignored, not the default choice.
|
||||
|
||||
---
|
||||
|
||||
**3. You want to confirm a process is still running WITHOUT affecting it in any way. What do
|
||||
you use?**
|
||||
|
||||
*Answer:* `kill -0 <pid>` — sends no real signal, only checks whether the PID still exists.
|
||||
|
||||
---
|
||||
|
||||
**4. Why might understanding processes matter for understanding containers later?**
|
||||
|
||||
*Answer:* A container is, underneath, mostly an isolated process (or small process tree) — a
|
||||
container "exiting" is really its main process exiting, the exact same event this module's Lab
|
||||
causes on purpose.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Question 2 is worth confirming explicitly — "kill -9 by default" is a common,
|
||||
worth-correcting habit.
|
||||
|
||||
**Approximate timing:** 5 minutes.
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
# Guided Exercise: Choosing the Right Signal
|
||||
|
||||
This is a paper exercise — commit to an answer before checking it in the Lab.
|
||||
|
||||
## Your task
|
||||
|
||||
For each situation, decide: plain `kill` (SIGTERM) first, or is `kill -9` (SIGKILL) actually
|
||||
justified?
|
||||
|
||||
1. A normal web server process you want to shut down for a routine restart.
|
||||
2. A process that received SIGTERM two minutes ago and is still running, showing no signs of
|
||||
responding.
|
||||
3. A script you just started by mistake, that hasn't done anything harmful yet.
|
||||
|
||||
## A worked shape to check yourself against
|
||||
|
||||
1. Plain `kill` — give it the chance to shut down cleanly (close connections, flush data, etc).
|
||||
2. `kill -9` is now justified — you already gave it the polite chance, and it didn't respond.
|
||||
3. Plain `kill` first, still — "just started" doesn't change the default; SIGKILL is a last
|
||||
resort, not a shortcut for convenience.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Item 3 sometimes trips students who reach for `-9` out of impatience — worth
|
||||
reinforcing that SIGTERM-first is the discipline REGARDLESS of urgency, since a process that
|
||||
ignores SIGTERM is the actual signal that escalation is warranted.
|
||||
|
||||
**Approximate timing:** 8-10 minutes live discussion; 5 minutes self-paced.
|
||||
@@ -0,0 +1,27 @@
|
||||
# Reflection: You Just Stopped a Real Process, Not a Simulation
|
||||
|
||||
## Reflection questions
|
||||
|
||||
1. Before checking `kill -0` after your `kill`, how confident were you the process was
|
||||
actually gone? What would you have done if it hadn't stopped?
|
||||
2. Why do you think SIGTERM-before-SIGKILL matters more for a real production service than for
|
||||
a throwaway script in a Lab?
|
||||
|
||||
## Real Ops takeaway
|
||||
|
||||
**"I ran the kill command" is not the same as "I confirmed the process is gone."** This module's
|
||||
Lab required you to verify state, not just take an action and assume it worked — that
|
||||
verification step is exactly what separates a confident operator from someone hoping for the
|
||||
best. You'll repeat this exact pattern (act, then verify) constantly for the rest of ROOT_,
|
||||
starting as soon as ROOT 04's containers and continuing all the way through Kubernetes
|
||||
troubleshooting.
|
||||
|
||||
This module is complete. Continue to the next module.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Discussion questions for a live classroom:** question 2 is a good bridge to ROOT 06's later
|
||||
discussion of graceful shutdown/`terminationGracePeriodSeconds` — plant the idea now without
|
||||
naming Kubernetes specifics yet.
|
||||
|
||||
**Approximate timing:** 5-8 minutes live discussion; 5 minutes self-paced writing.
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
# Users and Permissions Foundations
|
||||
|
||||
## Users, groups, and ownership
|
||||
|
||||
Every file on Linux has an **owner** (a user) and a **group**. `ls -la` shows both: the third
|
||||
column is the owning user, the fourth is the owning group. Multiple users can belong to the same
|
||||
group, which is how permissions get shared across a team without giving every individual user
|
||||
direct ownership.
|
||||
|
||||
## Read, write, execute — for three different audiences
|
||||
|
||||
Every file has permissions for THREE separate audiences: the owner, the group, and everyone
|
||||
else ("other"). Each audience gets some combination of:
|
||||
|
||||
- **r**ead — can view the file's contents (or list a directory's contents)
|
||||
- **w**rite — can modify the file (or create/delete files inside a directory)
|
||||
- e**x**ecute — can run the file as a program (or enter/traverse into a directory)
|
||||
|
||||
`ls -la` shows this as a 10-character string like `-rw-r--r--`: the first character is the file
|
||||
type, then three groups of `rwx` for owner/group/other.
|
||||
|
||||
## Reading and setting permissions as octal numbers
|
||||
|
||||
Each `rwx` triplet can be written as one digit: read=4, write=2, execute=1, summed together.
|
||||
`rw-` = 4+2 = `6`. `r--` = `4`. So `644` means: owner can read+write (6), group can read-only
|
||||
(4), everyone else can read-only (4) — a very common, sensible default for a regular file.
|
||||
`chmod 644 <file>` sets exactly that.
|
||||
|
||||
## `sudo`: temporary elevated privilege
|
||||
|
||||
Rather than logging in AS the root user (with permanent full access to everything), `sudo
|
||||
<command>` runs just that one command with elevated privileges, after checking you're allowed
|
||||
to. This is deliberate: the less time you spend with full privileges, the smaller the window for
|
||||
a mistake (or a compromised session) to do serious damage — the exact same reasoning behind
|
||||
"least privilege," which this module introduces by name for the first time.
|
||||
|
||||
## Least privilege: the principle, not just the command
|
||||
|
||||
**Least privilege** means: grant exactly the access something needs to do its job, and nothing
|
||||
more. A user who only needs to read a file shouldn't also be able to write it. A process that
|
||||
only needs to read one directory shouldn't have write access to the whole filesystem. This
|
||||
principle isn't specific to Linux permissions — you'll meet it again, in a much more elaborate
|
||||
form, in ROOT 09's Kubernetes RBAC and SecurityContext content. This module is where it starts.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Lesson goal:** Build real fluency with rwx/octal permissions AND plant the least-privilege
|
||||
PRINCIPLE explicitly — not just command syntax, since ROOT 09 depends on students already
|
||||
having this vocabulary.
|
||||
|
||||
**Approximate timing:** 15 minutes read; 10 minutes live.
|
||||
|
||||
**Common misconception to watch for:** students often memorize "644 = read/write" without being
|
||||
able to derive it from 4+2+1 — walk through the arithmetic explicitly rather than treating the
|
||||
numbers as magic.
|
||||
|
||||
**Discussion question:** "Why not just make everything readable and writable by everyone, to
|
||||
avoid permission headaches?" (Good setup for the least-privilege principle — let students
|
||||
generate the security argument themselves before you confirm it.)
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
# Demonstration: A Real Permission Denial, and the Real Fix
|
||||
|
||||
## OBSERVE
|
||||
|
||||
A file is owned by root with permissions `600` (owner read/write only):
|
||||
|
||||
```
|
||||
$ stat -c '%a %U' data.txt
|
||||
600 root
|
||||
```
|
||||
|
||||
## HYPOTHESIS
|
||||
|
||||
Predict: can a different, non-root user read this file?
|
||||
|
||||
## INSPECT
|
||||
|
||||
```
|
||||
$ su - labuser -c "cat data.txt"
|
||||
cat: data.txt: Permission denied
|
||||
```
|
||||
|
||||
Confirmed — `600` grants nothing to group or other, so `labuser` (neither the owner nor root)
|
||||
is correctly denied.
|
||||
|
||||
## CHANGE
|
||||
|
||||
Grant read access to everyone, while keeping write access limited to the owner:
|
||||
|
||||
```
|
||||
$ chmod 644 data.txt
|
||||
$ stat -c '%a' data.txt
|
||||
644
|
||||
```
|
||||
|
||||
## VERIFY
|
||||
|
||||
```
|
||||
$ su - labuser -c "cat data.txt"
|
||||
sensitive
|
||||
```
|
||||
|
||||
The exact same command that failed before now succeeds — the permission bits, not the file's
|
||||
actual content, were the only thing that changed.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Demo goal:** Show a permission denial as a real, reproducible fact (not just a described
|
||||
error message) and the exact minimal fix, not an overcorrection like `chmod 777`.
|
||||
|
||||
**Approximate timing:** 8 minutes live.
|
||||
|
||||
**Common mistake to watch for:** students reaching for `chmod 777` "to make the error go away"
|
||||
— explicitly name why that's wrong (it also grants WRITE to everyone, not just the read access
|
||||
actually needed) and connect it back to least privilege.
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
# Knowledge Check: Users and Permissions Foundations
|
||||
|
||||
---
|
||||
|
||||
**1. A colleague says "permission denied" and fixes it with `chmod 777`. What's wrong with
|
||||
that fix?**
|
||||
|
||||
*Answer:* `777` grants read, write, AND execute to the owner, group, AND everyone else — almost
|
||||
always far more access than the actual problem required. It "fixes" the symptom while violating
|
||||
least privilege, usually granting write access nobody asked for.
|
||||
|
||||
---
|
||||
|
||||
**2. What does the permission `644` actually mean, broken down?**
|
||||
|
||||
*Answer:* Owner: read+write (4+2=6). Group: read-only (4). Other: read-only (4). Nobody but the
|
||||
owner can modify the file; everyone can read it.
|
||||
|
||||
---
|
||||
|
||||
**3. Why does `sudo <command>` exist instead of just logging in as root permanently?**
|
||||
|
||||
*Answer:* It grants elevated privilege only for the one command that needs it, minimizing the
|
||||
time spent with full access — reducing the chance a mistake (or a compromised session) causes
|
||||
serious, wide damage.
|
||||
|
||||
---
|
||||
|
||||
**4. State the least-privilege principle in your own words.**
|
||||
|
||||
*Answer:* Grant exactly the access something needs to do its job, and nothing more.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Question 1 is worth discussing as a group — most students have seen `chmod
|
||||
777` somewhere online as "the fix," and unlearning that habit early matters.
|
||||
|
||||
**Approximate timing:** 5 minutes.
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
# Guided Exercise: Pick the Minimal Permission, Not the Easy One
|
||||
|
||||
This is a paper exercise — commit to an answer before checking it in the Lab.
|
||||
|
||||
## Your task
|
||||
|
||||
For each situation, write the exact `chmod` octal value that grants the MINIMUM permission
|
||||
actually needed:
|
||||
|
||||
1. A private SSH key file — only the owner should ever read or write it, nobody else, no
|
||||
execute for anyone.
|
||||
2. A shared script every user on the system should be able to run, but only the owner should be
|
||||
able to edit.
|
||||
3. A log file every user should be able to read (for troubleshooting), but only a logging
|
||||
service (the owner) should write to it.
|
||||
|
||||
## A worked shape to check yourself against
|
||||
|
||||
1. `600` — owner read+write (6), nothing for group or other (0, 0). This is the real, standard
|
||||
permission for private SSH keys — SSH itself refuses to use a key file that's more open than
|
||||
this.
|
||||
2. `755` — owner read+write+execute (7), group and other read+execute (5, 5) so they can run it
|
||||
but not modify it.
|
||||
3. `644` — owner read+write (6), group and other read-only (4, 4).
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Item 1 is a good moment to mention that this isn't a hypothetical rule — SSH
|
||||
genuinely refuses to use an overly-permissive private key file, a real-world consequence of
|
||||
exactly this principle.
|
||||
|
||||
**Approximate timing:** 8-10 minutes live discussion; 5 minutes self-paced.
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
# Reflection: Least Privilege Is a Habit, Not a One-Time Fix
|
||||
|
||||
## Reflection questions
|
||||
|
||||
1. Before this module, what would you have done the first time you hit a "permission denied"
|
||||
error? How does that compare to what you'd do now?
|
||||
2. Name one place outside of file permissions where you'd expect "grant only what's needed" to
|
||||
matter (a user account, an API key, a service's network access — anything).
|
||||
|
||||
## Real Ops takeaway
|
||||
|
||||
**Least privilege is the single idea from this entire module that reappears the most across
|
||||
the rest of ROOT_.** File permissions are just the first, most concrete place you'll apply it —
|
||||
ROOT 09 applies the exact same principle to Kubernetes RBAC (what can a user or workload
|
||||
actually DO) and SecurityContext (what can a container actually access). Getting comfortable
|
||||
asking "what's the minimum this actually needs" now, on a file you can see and touch, makes that
|
||||
same question much easier to ask later about something more abstract, like a ServiceAccount's
|
||||
permissions.
|
||||
|
||||
This module is complete. This is the end of ROOT 01 — continue to ROOT 02: Linux
|
||||
Administration & Networking.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Discussion questions for a live classroom:** question 2 works well as a round-robin — the
|
||||
variety of answers (API keys, database credentials, network firewall rules) is itself useful
|
||||
evidence of how far this one principle travels.
|
||||
|
||||
**Approximate timing:** 5-8 minutes live discussion; 5 minutes self-paced writing.
|
||||
|
||||
**Milestone note:** this is the last module of ROOT 01 — consider a short recap of the whole
|
||||
course's arc (hardware → shell → filesystem → files/text → processes → permissions) before
|
||||
moving on, so students see it as one coherent foundation rather than six separate topics.
|
||||
+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.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user