Files
rootnode-academy/docs/ROOT_FOUNDATIONS_LAB_STRATEGY.md
ced4568andClaude Sonnet 5 3c815cfeea docs(foundations): define ROOT 01-04 architecture and Lab strategy
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]>
2026-08-26 12:59:00 -05:00

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