How fast can one box spin up a thousand sandboxes?
I put Tencent's CubeSandbox on a low-end dedicated box and measured every part of the spin-up path. Warm resume ~20 ms, cold create ~72 ms, a 40-wide cold burst at p50 ~490 ms (p99 over a second), a thousand create-delete cycles in 23 seconds, and a density wall at 165 concurrent. Plus how it lines up against the managed sandboxes for coding and RL, and why I ran it on bare metal.
July 2026. Real runs on one Hetzner AX41. Ryzen 5 3600, 6 cores / 12 threads, 62 GB, Ubuntu 26.04 with KVM. CubeSandbox v0.5.1, one-click install. Raw JSON kept, nothing mocked.
CubeSandbox is Tencent's microVM sandbox runtime for agents. KVM, not containers. It claims sub-60ms cold starts with hardware isolation. I wanted that number on hardware anyone can rent, so I grabbed a low-end dedicated box and measured the whole spin-up path myself. Cold create, warm resume, both under load, a thousand create-delete cycles, and the point where concurrent density stops.
A sandbox here is a small virtual machine with its own kernel, thrown away when you're done. That VM boundary is the point: code the model wrote can't reach the host. Two ways to get one running.
Two spin-up numbers, not one
There isn't one number. There are two, and they're 5x apart.
Cold create means schedule the sandbox, restore its rootfs, wait for the agent inside to answer. That's ~72 ms for one sandbox on this CPU. Warm resume is a different thing. Pause a sandbox, then resume it. CubeSandbox writes the paused VM's memory to disk, so resume is a disk-backed restore, and it came back in ~20 ms here with the snapshot hot in the host page cache. That's the path that beats 50 ms. Keep a small pool paused, resume on demand, and a sandbox feels instant. When the snapshots aren't cached the number climbs, which shows up at scale later.
The word that matters is ideal. That 490 ms bar isn't a single sandbox's boot time. It's roughly the p50 wait when you fire 40 cold creates at once onto 12 hardware threads, and the p99 in that run runs past a second. I didn't profile enough to split queue time from slower service under contention. The point holds the other way round: a user who shows up alone sees 15 to 72 ms, and you only reach the high numbers by stampeding the box.
Why I used bare metal
CubeSandbox needs /dev/kvm. These are real microVMs with their own kernels, not containers, so you need hardware virtualization exposed to the OS. Native KVM on this box (svm plus a KVM kernel) gave me a controlled host with nothing between me and the metal. It's not a hard requirement, though: CubeSandbox ships a PVM mode that runs on ordinary x86-64 cloud VMs without exposed VT-x/AMD-V. Bare metal was just the clean way to measure.
Whatever the exact cause, throughput plateaued around 12 concurrent creates on this 6-core / 12-thread host. I didn't profile enough to pin the knee entirely on single-threaded boot work, but the practical effect is the same: past ~12, more concurrency buys latency, not throughput. And on a shared vCPU, steal from a noisy neighbor turns a clean 72 ms into a jittery tail. That's the other reason I wanted a dedicated core, a number I can repeat.
The snapshot path hits disk. Pause flushes a sandbox's memory to disk, resume reads it back. My box had a wrinkle: both NVMes were consumed by the OS RAID, so the XFS CubeSandbox wants ran on a loopback image. I assumed that loop device was a tax and left it as a caveat. Then I measured it. It isn't, and the real cause is more interesting. That's the last section.
Containers give you density anywhere. A VM boundary per sandbox is the part you're paying KVM for.
What one user waits as the crowd grows
Fix the batch, sweep concurrency 1 to 50. Per-user latency is flat, ~70 to 140 ms, until concurrency hits 12. That's the thread count. Then it climbs straight. The knee is the whole story. Below your cores everyone gets a fast sandbox. Above it they wait in line.
Throughput says the same thing backwards. It caps at ~40 sandboxes/sec at the knee and never gets better. Concurrency past your cores buys latency, not throughput. So the rule is boring. Keep concurrent spin-ups near your core count. Everything else goes behind a warm pool.
A thousand cycles in 23 seconds
Careful what this measures. I ran 100, 200, 500, 1000 create-then-delete cycles at concurrency 40. At any instant ~40 sandboxes exist, not the whole batch. Every run hit 100%. Completed-cycle throughput stayed flat around 40/sec no matter the batch, so wall-clock is linear. A thousand create-delete cycles finished in 23.2 seconds. That's a sustained churn number, not a thousand sandboxes alive at once. Alive-at-once is a different limit, the density section below.
Per-user latency inside those runs barely moves from 100 to 1000. Latency comes from concurrency, which I held at 40. Not from the total count.
Here's the spread on one full run, all thousand creates bucketed. Most land tight, a thin tail drags out from queuing.
| Batch | Success | Wall | Throughput | p50 | p95 | p99 |
|---|---|---|---|---|---|---|
| 100 | 100% | 2.7s | 37/s | 490 | 1103 | 1203 |
| 200 | 100% | 4.9s | 41/s | 494 | 949 | 1294 |
| 500 | 100% | 12.2s | 41/s | 508 | 1161 | 1484 |
| 1000 | 100% | 23.2s | 43/s | 486 | 1067 | 1344 |
latency in ms · create-delete · concurrency 40 · 512 MB sandbox-code template
The density wall
Making and freeing a thousand is easy. Holding a thousand alive is where the box pushes back. Ramping create-only, it stopped near 165 concurrent before CubeMaster started returning no more resource. I didn't isolate which check fired. CubeMaster weighs both a declared-memory quota and actual host memory, and this run doesn't tell me which one tripped first.
What I can say is the idle guests were cheap. Across the run, host MemAvailable dropped about 15.5 MB per sandbox created, which fits CubeSandbox's copy-on-write snapshot sharing. Idle sandboxes reserved far more than they touched. That's an idle-density observation, not a claim about loaded guests. Under real workload, density still comes down to RAM and what each guest actually uses.
The lever for "thousands per node" is the guest's real footprint and the quota math, not raw RAM. My image wouldn't boot under 512 MB, so 165 was my wall on this box. A slimmer guest, or memory overcommit, moves it.
CubeSandbox's own benchmark separates idle overhead from loaded density. Worth reading if you care about the ceiling under load.
Where this fits, next to the managed options
I didn't run the managed products. No numbers for them here. This is only where I'd reach for each.
Vercel's Sandbox runs untrusted, agent-generated code in Firecracker microVMs, with persistent sandbox state and snapshots. If I were already on Vercel, I'd use it and skip running the control plane.
Daytona is a managed programmable sandbox for agent code execution, with container, VM, GPU, and BYOC options. I'd use it when I want a hosted sandbox API and don't want to own the control plane.
Cloud Run runs serverless containers. First-gen instances use gVisor; second-gen run inside a microVM. Services scale to zero by default unless you set minimum instances. I'd use it for a service or a bursty job, not for checkpointing a live coding environment, since it doesn't expose a general pause-resume sandbox API.
For coding agents you want a real filesystem, package installs, and the freedom to run code you didn't write. I don't want model-written code sharing the host kernel. A microVM gives me a harder boundary. Baking Claude Code, Codex, and OpenCode into the template means the sandbox is the agent, ready on resume.
For RL training the win is the snapshot. Thousands of parallel environments, and every rollout needs a clean reset. Cold-booting a fresh env each episode is the slow path. Snapshot a golden environment once, then clone or roll back from that snapshot per rollout, and reset becomes a memory restore instead of a boot. Density decides how many rollouts fit per box, and that's the memory story from two sections up. It's a knob you own on your own hardware.
The trade is the obvious one. Managed means someone else runs the metal and eats the pager. Self-hosted on KVM means you keep the 20 ms path, the snapshot knobs, and the density ceiling. You also keep the RAID that made me put XFS on a loop device.
What each sandbox runs
The benchmark ran a plain code template. But these are full Linux microVMs, so I baked real environments in too. One template with Claude Code, Codex, and OpenCode installed. A desktop variant running Xvfb, fluxbox, noVNC, plus OpenCode's own web UI, all reachable in a browser through the built-in proxy. Building a template is one create-from-image line against a local registry. The snapshot gets taken once the health probe passes. So spinning up a sandbox can mean spinning up a coding agent that's ready to go, not an empty VM.
What makes a warm resume slow (the cache, not the disk)
I blamed the loopback XFS for my slow at-scale resumes. So I measured it, and I was wrong.
fio on the loopback XFS actually beats the direct filesystem on this box: 199k vs 146k 4K random-read IOPS, 8.0 vs 3.3 GB/s sequential, both with O_DIRECT. The loop device isn't a tax. A native NVMe partition wouldn't help.
The real variable is the page cache. A single resume is ~20 ms with the snapshot hot in RAM, ~94 ms cold off disk. At 60 resumes at once it's 33 ms p50 hot and 871 ms p50 cold, because now the box is pulling 60 snapshots back from disk and re-mapping their memory. Same fast disk, 26x apart.
So a warm pool only stays fast while its snapshots stay resident. That's a RAM and working-set problem, not a faster-disk problem. Size the pool to what fits in cache and the sub-50 ms path holds even under a burst. Let it fall out of cache and every wake pays the disk read again.
In production I'd cap concurrent cold creates around the knee, 12 here, size the paused pool to what stays cached, and remember paused sandboxes hold their quota by default so the pool isn't free. The ~20 ms cached-resume path is real. The 490 ms one is self-inflicted.
Rig: Hetzner AX41. AMD Ryzen 5 3600 (6C/12T), 62 GB, Ubuntu 26.04, KVM, 2×512 GB NVMe RAID1. Method: CubeSandbox v0.5.1 one-click, single node. Latency is the full E2B-compatible POST /sandboxes round-trip. Storage via fio (O_DIRECT), provisioning via the project's cube-bench, density and resume via a stdlib harness. Raw JSON kept locally.