Appearance
Resources per Student
What one student's lab costs the host in CPU, memory and disk, how that adds up against the single Ampere VM, and which numbers are hard limits versus admission-time reservations.
How these numbers were collected
Limits and reservations come from the repository (profiles.py, config.py, provision.py, manual/02-gateway-and-wazuh.md). The host totals, LXD profile settings, image sizes and shared-service memory were read from the live host on 2026-10-10 with read-only commands (free, lscpu, lxc profile show, lxc info, lxc image list, GET /capacity). No lab was running at the time, so actual per-lab usage under load is not measured here and is marked Needs Verification.
One student = one lab = three containers
Each student can have exactly one live lab (idx_pods_one_active_per_student). A lab is three unprivileged LXD containers on the student's own OVN network, created from golden images (provision.py):
| Container | Golden image | LXD profile | vCPU cap | Memory cap | Image size (live) |
|---|---|---|---|---|---|
pod-<student>-kali | kali-base | pod-security-base | 2 | 2 GB | 4.46 GiB |
pod-<student>-meta | meta-base | pod-target-base | 1 | 1 GB | 606 MiB |
pod-<student>-dvwa | dvwa-base | pod-target-base | 1 | 1 GB | 817 MiB |
| Total per student | 4 vCPU (cap) | 4 GB (cap) | 5.9 GiB of images, shared by every lab |
- The caps are
limits.cpuandlimits.memoryon the two LXD profiles (manual 02, Step 2). The live profiles match the repository exactly. POD_RAM_MB = 4096in both profiles inprofiles.pyis the same 4 GB, so the admission check reserves memory equal to the lab's combined cap.- Kali gets the larger share because it runs the student's tools (Nmap, Metasploit,
sqlmap) and the tmux terminal sessions. meta and dvwa run only the vulnerable services and a Wazuh agent; dvwa also runs DVWA and MariaDB in Docker, which is why its profile allowssecurity.nesting.
What each number really means
| Resource | Per student | Kind | Enforced by |
|---|---|---|---|
| Memory | up to 4 GB (2 + 1 + 1) | Hard cap per container (cgroup); a container that exceeds its cap is OOM-killed inside the container, not the host | LXD limits.memory |
| Memory reserved at start | 4096 MB, plus a host buffer of 1536 MB that must stay free | Admission check: MemAvailable must be at least labs-to-add × POD_RAM_MB + RAM_BUFFER_MB (5,632 MB for one lab) | capacity.py, pods_router.py |
| CPU | up to 4 vCPU (2 + 1 + 1) | Cap, not a reservation. The host has only 2 vCPU, so one lab's caps already exceed the host; containers share the CPUs and are throttled by the scheduler, not refused | LXD limits.cpu |
| Disk | 7,168 MB reserved | Accounting reservation. Containers are btrfs copy-on-write clones of the golden images, so a new lab uses almost no new disk; it grows with what the student writes. There is no per-container root size limit | POD_STORAGE_MB, storage gate |
| Network | one /24 (10.0.(50+N).0/24), one OVN network with its pod-N-egress ACL, one peer to the monitoring network, one return route inside wazuh-manager | slot; all removed at teardown | pod_net.py |
| SIEM | a Wazuh agent on meta, and on dvwa only if its image ships one, on the shared manager | removed at teardown (needs the provisioner user) | provision.py, wazuh_client.py |
| DVWA proxy | one host port, 18300 + pod_id | removed at teardown | LXD proxy device |
| Time | 30 / 45 / 45 / 60 minutes (Scenarios 1 to 4) | Hard limit: the reaper destroys the lab | SCENARIO_TTL_MINUTES, reaper.py |
| Database | a few rows per poll | grows while milestones are unpassed: about 1 to 1.3 rows per second into each of milestone_verification and audit_log (see F10) | score_poller.py |
The host budget
The live host is one OCI Ampere A1 VM:
| Host resource | Total (live) |
|---|---|
| CPU | 2 vCPU (Arm Neoverse-N1) |
| Memory | 11.9 GiB (11,927 MB), plus zram and swap from setup_zram_and_swap.sh |
| Boot disk | 45 GB (/, 62% used) |
LXD storage pool cyberrange | 150 GiB btrfs, 20.4 GiB used (mostly golden images and their rollback copies) |
Memory is the resource that sets capacity. With no lab running, the live GET /capacity reported:
| Item | MB |
|---|---|
| Host memory (total) | 11,927 |
Used by the host, API, portal, bridge, guacamole (nginx + Keycloak, about 1.2 GiB) and wazuh-manager (about 1.2 GiB) | about 2,980 |
MemAvailable with no lab running | 8,947 |
Needed to admit one lab (POD_RAM_MB + RAM_BUFFER_MB) | 5,632 |
| Left over after one lab's reservation | about 3,300 |
| Needed for a second lab | another 4,096 |
That is why the live profile is oci_12gib with MAX_PODS = 1: one student at a time. The onprem profile assumes a bigger machine and allows 3 labs (RAM_BUFFER_MB 2048). A comment in config.py says raising MAX_PODS above 3 needs a load test first.
Shared, not per student
These run once for everyone and are not part of any one lab's cost:
| Service | Where | Cap (live) | Memory in use (live, idle) |
|---|---|---|---|
| Gateway: nginx + Keycloak | guacamole container | 2 vCPU, 2 GB | 1.19 GiB |
| Wazuh manager | wazuh-manager container | 3 GB | 1.18 GiB |
| Provisioning API, portal (Next.js), terminal bridge | host user services | none | part of the host's ~3 GB |
| Golden images | LXD pool | n/a | 5.9 GiB of disk for the three current images |
Scaling notes
- More students at once means more RAM first. Each extra concurrent lab needs 4 GB more free memory. CPU is shared and only slows labs down; memory is what the admission check refuses on (
RAM_FULL). - Address space allows six labs (
pod_id1 to 6,pod-1-nettopod-6-net), whatever the RAM. - Labs end quickly now. With 30 to 60 minute limits instead of 8 hours, one slot can serve several students in a class period.
- Needs Verification: real memory and CPU use of a running lab (for example
lxc info pod-<student>-kaliduring a Metasploit session) has not been recorded. Measuring a running lab of each scenario would show how much of the 4 GB cap is actually used and whether a smaller cap or a second slot is safe.
Sources
cyberrange/src/provisioning/profiles.py, config.py, capacity.py, pods_router.py, provision.py, pod_net.py, reaper.py, score_poller.py, manual/02-gateway-and-wazuh.md (Step 2, LXD profiles; gateway and Wazuh container limits), cyberrange/deploy/host/setup_zram_and_swap.sh. Live host values read on 2026-10-10.