Compare commits
82
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 | ||
|
|
4c0a950afb | ||
|
|
7baf7074e5 | ||
|
|
777efd168d | ||
|
|
b38c46aaec | ||
|
|
be92be81cd | ||
|
|
f25535027c | ||
|
|
3c7f0ca59c | ||
|
|
9d382c95c5 | ||
|
|
5183a6af09 | ||
|
|
1c08a30c7f | ||
|
|
23f881884d | ||
|
|
f89a769540 | ||
|
|
4bc202e5f3 | ||
|
|
1b6da0a966 | ||
|
|
17160da89e | ||
|
|
611e1ac4ba | ||
|
|
895e16fea4 |
+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/kubectl && \
|
||||||
ln -sf /usr/local/bin/k3s /usr/local/bin/crictl
|
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 ─────────────────────────────────────────────────────────────────
|
# ── App files ─────────────────────────────────────────────────────────────────
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|||||||
@@ -163,6 +163,19 @@ its core:
|
|||||||
classes from `/admin` (presence, Lab/learning status, help-request queue, announcements,
|
classes from `/admin` (presence, Lab/learning status, help-request queue, announcements,
|
||||||
guidance-only current-activity) — RootNode owns identity/coordination, an external service
|
guidance-only current-activity) — RootNode owns identity/coordination, an external service
|
||||||
(Meet/Zoom/Teams/etc.) owns the actual video/voice.
|
(Meet/Zoom/Teams/etc.) owns the actual video/voice.
|
||||||
|
- Students can now be enrolled in a self-paced program (as of Phase 5B) — "My Programs" on the
|
||||||
|
student dashboard, entitlement enforced server-side (pending/future/expired/cancelled
|
||||||
|
enrollments cannot launch a Lab no matter what the browser sends), completion server-derived
|
||||||
|
from the existing capstone rules. `/admin` gained a Programs area (program authoring, manual
|
||||||
|
enrollment, activate/cancel). A separate, provider-neutral `POST /api/integrations/enrollments`
|
||||||
|
boundary — server-to-server only, no browser session involved — is what the future public
|
||||||
|
`rootnodeacademy.com` website/checkout will call to create these enrollments; see
|
||||||
|
`docs/PHASE5B_SELF_PACED_ENROLLMENT.md` and `docs/WEBSITE_ENROLLMENT_INTEGRATION.md`.
|
||||||
|
- As of Phase 5C, RootNode actually tells students what's happening — real activation,
|
||||||
|
enrollment-confirmation, and password-reset email, delivered asynchronously through a
|
||||||
|
provider-neutral notification worker (Google Workspace via the Gmail API in production, a
|
||||||
|
deterministic dev provider everywhere else — no test/dev run ever emails a real person). See
|
||||||
|
`docs/PHASE5C_ENROLLMENT_COMMUNICATIONS.md` and `docs/RUNBOOK_GOOGLE_WORKSPACE_EMAIL.md`.
|
||||||
|
|
||||||
**Never publish the raw privileged container** — front it with isolation (that's what the
|
**Never publish the raw privileged container** — front it with isolation (that's what the
|
||||||
Gateway is). The one-container `docker run --privileged -p 7554:80 ...` command above stays
|
Gateway is). The one-container `docker run --privileged -p 7554:80 ...` command above stays
|
||||||
@@ -200,7 +213,8 @@ Key properties of the platform (not this image):
|
|||||||
Production topology and what's built vs. not yet: `docs/LAB_ARCHITECTURE.md`,
|
Production topology and what's built vs. not yet: `docs/LAB_ARCHITECTURE.md`,
|
||||||
`docs/PHASE1_PLATFORM_FOUNDATION.md`, `docs/PHASE2A_GATEWAY_AND_STUDENT_FLOW.md`,
|
`docs/PHASE1_PLATFORM_FOUNDATION.md`, `docs/PHASE2A_GATEWAY_AND_STUDENT_FLOW.md`,
|
||||||
`docs/PHASE3A_IDENTITY_AND_INSTRUCTOR_ACCESS.md`, `docs/PHASE3B_LEARNING_AND_ASSESSMENT.md`,
|
`docs/PHASE3A_IDENTITY_AND_INSTRUCTOR_ACCESS.md`, `docs/PHASE3B_LEARNING_AND_ASSESSMENT.md`,
|
||||||
`docs/PHASE5A_STUDENT_IDENTITY_REMOTE_CLASSROOM.md`.
|
`docs/PHASE5A_STUDENT_IDENTITY_REMOTE_CLASSROOM.md`, `docs/PHASE5B_SELF_PACED_ENROLLMENT.md`,
|
||||||
|
`docs/PHASE5C_ENROLLMENT_COMMUNICATIONS.md`.
|
||||||
|
|
||||||
The production infrastructure package (Proxmox/Ansible/Terraform/firewall/monitoring/backup/CI)
|
The production infrastructure package (Proxmox/Ansible/Terraform/firewall/monitoring/backup/CI)
|
||||||
that runs all of this on real hardware lives in `infra/` — see
|
that runs all of this on real hardware lives in `infra/` — see
|
||||||
|
|||||||
+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.
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user