# 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- && ...` 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 |