Skip to content

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):

ContainerGolden imageLXD profilevCPU capMemory capImage size (live)
pod-<student>-kalikali-basepod-security-base22 GB4.46 GiB
pod-<student>-metameta-basepod-target-base11 GB606 MiB
pod-<student>-dvwadvwa-basepod-target-base11 GB817 MiB
Total per student4 vCPU (cap)4 GB (cap)5.9 GiB of images, shared by every lab
  • The caps are limits.cpu and limits.memory on the two LXD profiles (manual 02, Step 2). The live profiles match the repository exactly.
  • POD_RAM_MB = 4096 in both profiles in profiles.py is 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 allows security.nesting.

What each number really means ​

ResourcePer studentKindEnforced by
Memoryup 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 hostLXD limits.memory
Memory reserved at start4096 MB, plus a host buffer of 1536 MB that must stay freeAdmission 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
CPUup 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 refusedLXD limits.cpu
Disk7,168 MB reservedAccounting 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 limitPOD_STORAGE_MB, storage gate
Networkone /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-managerslot; all removed at teardownpod_net.py
SIEMa Wazuh agent on meta, and on dvwa only if its image ships one, on the shared managerremoved at teardown (needs the provisioner user)provision.py, wazuh_client.py
DVWA proxyone host port, 18300 + pod_idremoved at teardownLXD proxy device
Time30 / 45 / 45 / 60 minutes (Scenarios 1 to 4)Hard limit: the reaper destroys the labSCENARIO_TTL_MINUTES, reaper.py
Databasea few rows per pollgrows 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 resourceTotal (live)
CPU2 vCPU (Arm Neoverse-N1)
Memory11.9 GiB (11,927 MB), plus zram and swap from setup_zram_and_swap.sh
Boot disk45 GB (/, 62% used)
LXD storage pool cyberrange150 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:

ItemMB
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 running8,947
Needed to admit one lab (POD_RAM_MB + RAM_BUFFER_MB)5,632
Left over after one lab's reservationabout 3,300
Needed for a second labanother 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:

ServiceWhereCap (live)Memory in use (live, idle)
Gateway: nginx + Keycloakguacamole container2 vCPU, 2 GB1.19 GiB
Wazuh managerwazuh-manager container3 GB1.18 GiB
Provisioning API, portal (Next.js), terminal bridgehost user servicesnonepart of the host's ~3 GB
Golden imagesLXD pooln/a5.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_id 1 to 6, pod-1-net to pod-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>-kali during 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.

Cyber Range capstone documentation (Capstone 2 · Group 2 · A4102). Documents main at commit 6971373.