Skip to main content

Hardware sizing

Reference numbers for how much CPU and memory the helmdeck control plane and its session sidecars actually use, plus the per-profile knobs you can turn without forking the binary.

For the design rationale (why we use CPU profiles instead of per-pack numbers, how the autodetect heuristic was chosen, how it maps to Kubernetes), see ADR 045.

Minimums

Use caseCoresMemoryNotes
Control plane + web + repo packs only24 GiBI/O-bound packs run with ~1 core each; no video work.
Above + slide PDF render26 GiBslides.render is brief Chromium + PDF — fits in the IO profile.
Above + narrated MP4 (slides.narrate)48 GiBEncode + concat will use up to 3 cores on a 4-core box.
Above + hyperframes.render 1080p812 GiBHeadless Chromium + ffmpeg saturate at ~6 cores; render is heavy.
hyperframes.render 4k816 GiBBump the pack's MemoryLimit override via env (see below).

If you're under the recommended cores for a workload, helmdeck still runs — the render just takes proportionally longer. A 1-core host caps every profile at 1.0 cores by design (degraded but functional).

What each profile gets

The session runtime resolves a pack's CPUProfile to a concrete CPU cap at startup. The resolved numbers are logged once on boot:

{"msg":"session CPU profile caps","io_cores":1,"compute_cores":6}

io_bound (the default)

I/O-bound packs — Playwright sessions, HTTP orchestration, repo clones, OCR-by-HTTP-call. Default: 1.0 core. A second core would sit idle waiting on the network.

Used by: web.scrape, web.test, web.login, web.fetch, browser.interact, screenshot.url, scrape.spa, repo.fetch, repo.map, repo.push, fs.*, doc.ocr, doc.parse, podcast.generate, swe.solve, vision.*, desktop.*, slides.render, blog.publish, content.ground.

compute_bound

CPU-bound packs — video encode, multi-segment render, ffmpeg work. Default: clamp(host_cores - 1, 1, 6). Leaves one core for the host; caps at 6 because ffmpeg + Chromium saturate around there.

Host corescompute_bound capWhy
11.0Degraded but functional — gives it the only core.
21.0Leave one for the host.
43.0Three for the render, one for everything else.
86.0Saturation cap kicks in.
16+6.0More cores don't speed up the encode meaningfully.

Used by: hyperframes.render, slides.narrate.

Operator overrides

Pin a cap explicitly via env. Per-profile env wins over the autodetect heuristic. Garbage or non-positive values fall back to the heuristic — you can't accidentally cap a render at zero cores.

Env varEffect
HELMDECK_IO_CPU_LIMITFractional cores for every ProfileIO pack.
HELMDECK_COMPUTE_CPU_LIMITFractional cores for every ProfileCompute pack.

Examples:

# Big shared CI box — reserve 4 cores for everything else,
# let the render use the rest.
HELMDECK_COMPUTE_CPU_LIMIT=12

# Constrained dev laptop — keep IO packs on half a core so
# the host stays responsive during a Playwright run.
HELMDECK_IO_CPU_LIMIT=0.5

Per-pack MemoryLimit is hard-coded on each pack today (e.g. hyperframes.render = 4g, slides.narrate = 3g). If you need to override memory for a specific pack, set the pack's SessionSpec.MemoryLimit in code; an env-driven memory profile is a follow-up if it turns out to be needed.

Kubernetes

The same CPUProfile works on Kubernetes. A K8s session.Runtime implementation translates the resolved CPULimit into the Pod spec's resources.requests.cpu and resources.limits.cpu (set equal to guarantee scheduling). runtime.NumCPU() inside a Pod honors the cgroup cap, so a control-plane Pod running with cpu: "2" autodetects a compute cap of min(2-1, 6) = 1, which is the right answer for that Pod's neighborhood.

If you're scheduling compute-bound packs onto dedicated nodes (e.g. an autoscaled video-render node pool), put the per-profile env override on those nodes' Pod spec, not on the control plane.

See also