Resource limits¶
Each pack declares the blast radius it needs. freeze writes it into the plan; run
applies it as the container's limits.
Fields¶
| Field | Default | Minimum | Notes |
|---|---|---|---|
memory_mb |
2048 |
64 |
Swap is pinned to the same figure at run time. |
cpus |
2.0 |
> 0 |
|
pids |
512 |
16 |
Process count. |
Unknown keys are refused.
Declared per pack¶
A global default a pack cannot express a need for is a bad ceiling: a pack that genuinely wants 8 GB has nowhere to say so, and the operator either raises the cap for every pack at once or not at all.
Declaring it in the manifest makes the ceiling per pack and reviewable in the frozen
plan. A security reviewer reads it off plan.lock.json instead of pulling an image to
find it.
The defaults above match what comparable harnesses apply globally, so a pack that says nothing behaves the same.
Swap is capped too¶
memory_mb pins swap to the same figure.
A memory cap that leaves swap open is a cap the container walks straight through. Limiting RAM to 2 GB while leaving swap unbounded does not limit the pack; it makes it slow.
Processes¶
Nothing else here caps process count, and a pack that forks in a loop takes the host down
while staying comfortably inside its memory limit. pids is the limit that stops it.
Being killed is recorded¶
A pack killed by the memory cgroup is recorded as out_of_memory:
{"utc": "...", "event": "unit_finished", "run_id": "example_pack.r0",
"exit_code": 137, "termination": "out_of_memory", "egress_enforced": true}
Docker reports 137 for a killed container, and that is also what a timeout reports, so the
exit code alone cannot tell them apart. termination is carried separately for exactly
this reason. See Running packs.