ROOT_FOUNDATIONS_ARCHITECTURE.md designs all 26 modules across ROOT 01 (Computing & Linux Foundations), 02 (Linux Administration & Networking), 03 (Git & Collaborative Development), and 04 (Containers & Docker), using the existing course/module/activity model — outcomes, prerequisites, activities, and Real Ops connections for each, per docs/COURSE_AUTHORING_STANDARD.md. ROOT_FOUNDATIONS_LAB_STRATEGY.md documents the central finding this phase is built on: the existing scenario engine (backend/server.js's runCommand()) executes an arbitrary shell command string and checks its output — nothing about it is Kubernetes-specific. The existing `lab` activity type and scenario JSON format already fully support Linux and Git exercises with real, deterministic validation. No shell_lab/git_lab/container_lab activity type is built or needed. Also documents the container-runtime decision for ROOT 04, evaluated and tested empirically (not assumed) against the real Lab image and production resource caps — see the following commit for the implementation. Co-Authored-By: Claude Sonnet 5 <[email protected]>
7.9 KiB
ROOT Foundations Lab Strategy
Phase 6D §7/§10 — what the existing disposable Lab can safely teach for ROOT 01-04, evaluated
by actually reading backend/server.js and testing inside a real container, not assumed.
1. The scenario engine is already generic — no new activity type needed
POST /api/scenarios/:id/setup and POST /api/scenarios/:id/validate (backend/server.js)
run setup_commands[].command / validation.commands[].command through runCommand(), which
is a bare child_process.exec(cmd) — an arbitrary shell command string, checked against
expected_output by checkMatch() (exact/contains/not_contains/regex). Nothing about this
is Kubernetes-specific. Every existing scenario just happens to write kubectl ... as its
command string. isolation_type (shared_namespace etc.) is pure descriptive metadata — grepped
across the entire backend and frontend, it gates no actual behavior.
This means the existing lab activity type (module_activities.activity_type = 'lab',
completion_behavior = 'auto_scenario') and the existing scenarios/data/*.json format already
fully support Linux, networking-inspection, and Git exercises with real, deterministic
validation — a scenario file with "command": "test -f /root/lab/notes.txt && echo exists" is
exactly as valid as one with "command": "kubectl get pod ...". No shell_lab/git_lab/
container_lab activity type is built or needed. The speculative multi-type model the
authorization raised as a possibility is unnecessary — it would duplicate a generic mechanism
that already exists.
The only wrinkle: runCommand does not pass a cwd, so every command runs wherever the backend
process's cwd is; multi-step Linux/Git scenarios must cd /root/lab-<id> && ... inside each
command string (exactly how a real student would chain commands), not rely on shell state
persisting between setup and validate calls beyond what the filesystem/git/process state
itself carries.
2. What the Lab image already contains (verified from the real Dockerfile)
Ubuntu 22.04 base with curl wget git vim nano jq bash-completion iproute2 iptables iputils-ping procps htop mount kmod python3 nginx already installed, plus k3s (bundling
kubectl/crictl/containerd) started by scripts/entrypoint.sh. The student terminal execs
into this same container via node-pty — there is no separate "Linux sandbox," it's the same
shell k3s runs in.
Already usable with zero image changes: shell navigation, file manipulation (cp/mv/rm/
cat/grep/pipes/redirection), process inspection (ps/top/kill), permissions
(chmod/chown/sudo — the container runs as root, so a permission-teaching scenario should
useradd a second user in its setup_commands to demonstrate a real permission denial), and
network inspection (ip addr/ip route/ss/ping/curl).
Missing, needed for ROOT 02 Module 5 (DNS & HTTP): dig/nslookup are NOT installed
(dnsutils/bind9-dnsutils is absent from the current package list). This is a genuinely small,
justified Dockerfile addition (one more apt-get install package, same risk class as everything
else already there) — not a new mechanism. curl alone already covers the HTTP half.
3. Container runtime decision — tested, not assumed
Decision: install real Docker Engine (docker.io) inside the same already-privileged Lab
container, alongside k3s, as a second independent container runtime with its own nested
/var/run/docker.sock. Podman was not chosen — Docker Engine matches the actual ROOT 04
curriculum content (Docker CLI, Dockerfile, Compose terminology) and required no new privilege.
Why this doesn't weaken the security boundary: the student Lab container is already
launched with Privileged: true (platform/provisioner/src/lifecycle.js — required for k3s,
already the accepted threat model per docs/LAB_ARCHITECTURE.md §7: the Lab Host VM, not the
container boundary, is what actually contains a student). A second container runtime running
inside an already-fully-privileged guest adds no new host capability. The Lab Host's own
Docker socket is never touched or exposed — the nested dockerd started inside the guest
listens on its own /var/run/docker.sock inside that guest's filesystem namespace, entirely
separate from the Lab Host's real Docker daemon that the Provisioner itself talks to.
Tested empirically (not assumed) against the exact production resource caps
(resource-policy.yaml: 2 vCPU, 4096MB memory, 512 PIDs, Privileged: true): built
rootnode-academy:test fresh from the real Dockerfile, layered docker.io on top, launched
it under those exact docker run constraints, and ran real workloads inside:
| Step | Result | Memory | PIDs |
|---|---|---|---|
| k3s alone, ready | ✓ | 357MB | 36 |
+ dockerd --storage-driver=vfs started |
✓ | — | — |
docker run --rm hello-world |
✓ passed | — | — |
docker run -d -p 8080:80 nginx:alpine, curl localhost:8080 |
✓ 200, real HTML returned | 866MB | 159 |
docker build a real 2-line Dockerfile, docker run the result |
✓ passed | 884MB | 160 |
Comfortable headroom remained in all three dimensions (884MB/4096MB, 160/512 PIDs, CPU settled
after the image pull). --storage-driver=vfs was used deliberately — overlay2-on-overlay2
(the Lab Host's own overlay filesystem, nested inside another overlay driver) is a known source
of instability in Docker-in-Docker setups; vfs is slower per-layer but has no such nesting
hazard, and ROOT 04's exercises are small single-purpose images, not build-heavy CI workloads.
docker compose is NOT included by docker.io — Ubuntu's packaged Docker CLI does not ship
the Compose v2 plugin. Fixed the same way the Dockerfile already fetches k3s: download the
static docker-compose v2 binary directly (matching the existing "curl a static binary rather
than add a whole package repository" pattern already used for k3s) into
/usr/local/lib/docker/cli-plugins/docker-compose so docker compose works as a CLI subcommand.
What was NOT done: no change to the Provisioner, Lab Router, or Gateway. No change to
HostConfig (still Privileged: true, same CPU/memory/PIDs caps as every other Lab). No mount
of the Lab Host's /var/run/docker.sock into any container — verified this was never written
anywhere in this phase's changes.
4. Git strategy
git is already installed. All ROOT 03 exercises use local repositories created inside the
student's own Lab (git init in a scenario's setup_commands) — no Gitea account, no network
credentials, no dependency on RootNode's own canonical repository ever touching a student Lab.
A later module ("Remote Repositories") teaches clone/fetch/pull/push/origin conceptually
and against a second local bare repository created in the same Lab (git init --bare,
git remote add origin /root/lab-remote.git) to give students a genuine push/pull/clone
round-trip without needing any real network service, credential, or RootNode-adjacent
infrastructure — Step 9's explicit constraint.
5. Classification summary
| Capability | Classification |
|---|---|
| Shell/filesystem/process/permissions exercises | SUPPORTED NOW — zero image or code change |
Network inspection (ip, ss, ping, curl) |
SUPPORTED NOW |
DNS tooling (dig/nslookup) |
REQUIRES SMALL PLATFORM EXTENSION — one apt package added to the Lab Dockerfile |
| Git exercises (local + local-bare-remote) | SUPPORTED NOW — zero image or code change |
| Docker CLI / image build / single containers | REQUIRES SMALL PLATFORM EXTENSION — docker.io + entrypoint change to start dockerd, tested and proven safe/performant above |
| Docker Compose | REQUIRES SMALL PLATFORM EXTENSION — static docker-compose v2 binary, same pattern as k3s's own binary fetch |
A distinct shell_lab/git_lab/container_lab activity type |
NOT NEEDED — the existing generic lab + scenario-JSON model already covers all of the above |