Benchmarks
What boxer costs against other sandboxes and across its own backends, with the date and conditions of each measurement.
The latest measurements are from 2026-09-28. A no-op command in a running smolvm sandbox takes
33 ms through boxer run, against 29 ms for docker exec and 232 ms for a fresh docker run.
Every number on this page comes from one Apple M4 Mac (10 cores, 24 GB, macOS 26.5.2). The
scripts are in bench/. Each script writes its raw results beside it, and bench/README.md has the
full method. A number measured once is labelled a single sample.
| measurement | script | recorded | boxer build |
|---|---|---|---|
| A command in a running sandbox | make bench | 2026-09-28 | v1.0.0-55-g34ca729, smolvm 1.16.1 |
| Across boxer's backends | bench/backends.sh | 2026-09-28 (podman 2026-09-25) | v1.0.0-55-g34ca729 |
[urls] on and off | bench/urls.sh | 2026-09-28 | v1.0.0-55-g34ca729 |
| Starting a development stack | make bench-devserver | 2026-09-25 | not recorded |
| Several sandboxes at once | bench/parallel-clean.sh | 2026-09-25 | not recorded |
"Recorded" is the date the results were committed to bench/.
The two 2026-09-25 rows were not re-measured after the 2026-09-27 fixes, which changed how the
container backends publish ports, gave every smolvm machine an idle workload, and made boxer up
with [urls] on wait for the proxy and the route.
A command in a running sandbox
make bench runs a no-op and a set of ordinary workloads in an already-running sandbox. It takes
20 samples per cell and reports the median. Docker was 29.4.0.
| no-op | |
|---|---|
| seatbelt (the sandbox Claude Code and Codex use on macOS) | ~8 ms |
boxer run, smolvm | 33 ms |
docker exec into a running container | 29 ms |
docker run, a fresh container per command | 232 ms |
- boxer is within 4 ms of
docker execon every workload that is not I/O-bound. - boxer is about 7x faster than a container per command, because the sandbox per worktree stays warm.
- Seatbelt is faster. It filters syscalls around a host process and does not start another kernel.
- The worktree mount adds cost. A recursive grep takes 2.4x as long as in a warm container, and so does writing 200 files. The cost comes from virtiofs.
- boxer adds 19 to 27 ms over raw
smolvm machine exec, most of it starting thesmolvmprocess. - The rewrite hook that every intercepted command passes through takes 4 ms.
Starting a development stack
make bench-devserver times how long a Next.js dev server takes to answer HTTP 200. Every
contender gets node:24-alpine, 10 vCPU and 8 GB, an empty npm cache, the same lockfile and a
cleared .next.
| start a session (deps installed) | install dependencies | |
|---|---|---|
| no sandbox | 6.8 s | 3.5 s |
| boxer, smolvm | 7.6 s | 9.2 s |
| OrbStack (a container) | 10.8 s | 5.2 s |
You pay the session start every time. boxer adds 0.8 s over no sandbox. Installing dependencies is
slower in boxer because the guest writes through the mount. [prep] and the
host package-cache mounts move that work out of the guest.
Several sandboxes at once
bench/parallel-clean.sh runs at Vercel Sandbox's default shape, 2 vCPU and 4 GB each, with one
linked worktree per sandbox. Every cell starts from a wiped host.
| sandboxes | boxer, ready | OrbStack, ready |
|---|---|---|
| 1 | 7.6 s | 10.2 s |
| 3 | 22.3 s | 18.8 s |
| 5 | 26.3 s | 27.1 s |
Past the first sandbox, Next.js compiling on ten shared cores takes most of the time. One and three are single samples. Five is the median of three to four.
Across boxer's backends
bench/backends.sh runs one backend at a time, alone on the host, three samples each, with a
Next.js dev server per worktree at 2 vCPU and 4 GB. There is no Linux run of this benchmark.
These figures are not comparable with the table above. bench/parallel-clean.sh restarts OrbStack
and prunes its image cache before every cell and times plain docker run, so its container rows
include a cold runtime VM. bench/backends.sh empties every backend but does not restart it, and
times each one through boxer.
| backend | boundary | one worktree | three worktrees |
|---|---|---|---|
| docker | shared kernel | 4.3 s | 10.9 s |
Apple container | kernel per sandbox | 5.3 s | 14.4 s |
| smolvm | kernel per sandbox | 6.4 s | 15.6 s |
A kernel per sandbox costs about 1 to 2 s on the first worktree, and 3.5 to 4.7 s at three. docker
was the steadiest, with all three one-worktree samples at 4.3 s. Apple container had one slow
sample out of three (8.3 s against 5.3 s), as it did on 2026-09-25.
Podman was not rerun because the host had no podman machine. On 2026-09-25 it took 3.6 s for one
worktree and 25.9 s for three. Every other backend was 40 to 50% slower at three worktrees on
that day than on 2026-09-28, docker included, with the same app and script. The cause was host
state, which was not recorded. Compare rows within one run, not across the two.
bench/backends-2026-09-25.jsonl has that run.
[urls] on and off
bench/urls.sh brings up two worktrees that differ only in [urls] enabled, then times five
interleaved pairs of each call. The table shows medians.
boxer up on a running sandbox | warm boxer run | |
|---|---|---|
[urls] off | 14.2 ms | 30.7 ms |
[urls] on | 14.4 ms | 32.0 ms |
The difference is within the spread of the samples. The route is registered when the sandbox
starts, so a later boxer up finds it in place.
Memory
A sandbox serving a Next.js dev server touches about 1.2 GB inside the guest, 642 MB of it node's own heap, and does not grow to fill a larger allocation. That figure replicates across runs.
The whole-host measurement varied up to 2.1x within one configuration, and halving each guest's memory produced both the lowest and the highest figure. A microVM per worktree costs a kernel, a guest userland and a page cache more than a shared kernel. How much more on this workload is unmeasured.
Withdrawn
Repeated runs showed these three earlier claims were wrong:
- "About 243 MB per sandbox, crossover at six." This was measured on sandboxes running nothing. One serving a dev server costs about a gigabyte, most of it node.
- "Four times OrbStack's memory at five sandboxes." The harness ran every cell in one process, so the container runtime's VM had already grown and was charged only for marginal growth. From a wiped host the same cell read 2.2 to 2.9 GB, not 501 MB.
- "Halving the guest allocation saves memory." It produced the widest spread measured.
Each came from a single sample.
Caveats
- One host, one architecture. On a Mac,
docker execcrosses a VM boundary too. On a Linux host every container row would be cheaper and the comparison would change. - Cache state dominates cold start. A first run pulls the image. Every figure above is warm unless it says otherwise.
- Benchmarks do not fail CI. The evaluation tiers check whether boxer works.