Backends
smolvm, Apple container, docker and podman compared by isolation boundary, enforced features and start-up time.
Choose the runtime that runs each sandbox.
Not sure?
Keep the default, smolvm. It gives each sandbox its own kernel and a network allowlist. Already use
Docker and want to keep it? Set backend = "docker" and network.mode = "on", and see
Bring your Docker setup.
backend = "smolvm" # smolvm | container | docker | podmanBOXER_BACKEND overrides it for one command.
Compare backends
| backend | a sandbox is | kernel | egress allowlist | packs | fork | host edits reach a dev server |
|---|---|---|---|---|---|---|
smolvm (default) | a microVM | one per sandbox | yes | yes | yes | through polling, which boxer turns on |
container (Apple, macOS 26 on Apple Silicon) | a lightweight VM per container | one per sandbox | no | no | no | through polling, which boxer turns on |
docker | a container | shared by every sandbox on the machine | no | no | no | yes with OrbStack; Docker Desktop not measured |
podman | a container | shared by every sandbox on the machine | no | no | no | on a Mac, through polling, which boxer turns on |
- Kernel. An escape from a microVM lands in one guest. An escape from a container lands in the kernel every other sandbox on the machine uses. On a Mac, docker and podman run their containers in one Linux VM, so the sandboxes share that VM's kernel. On Linux they share the host's kernel.
- Egress allowlist. Only smolvm enforces
network.mode = "allowlist", the default. The other backends refuse it. Setnetwork.modeto"on"or"off"for them. - Packs. smolvm caches each environment pack, so
image_setupruns once per host. On the other backends it runs once per sandbox. - Fork.
boxer forkneeds copy-on-write VM branching. Only smolvm has it. - File watchers. A dev server reloads when its file watcher sees an edit. On smolvm, Apple
containerand podman's Mac VM, most watchers miss edits you make on your machine, so boxer setsWATCHPACK_POLLINGandCHOKIDAR_USEPOLLINGin the sandbox. Next.js's Turbopack has no polling that works there: runnext dev --webpack. See a dev server does not reload.
All four keep secrets off argv and mark the sandboxes they own with labels. Only smolvm has an egress log.
When a backend lacks a feature, boxer refuses with an error that names it. On docker and podman,
cpus and memory become the container's --cpus and -m limits.
Everything else works the same on all four: hooks, shims, the MCP server, tasks, URLs, devcontainer
support, [prep] and cache mounts. On every backend the sandbox sees the worktree, your mounts, and read-only package caches when [cache] is on. git, gh and ssh run on the host. Forwarded ports and the
portless proxy bind 127.0.0.1. See the security model.
Choose a backend
| use | when |
|---|---|
smolvm | The default. You want the egress allowlist, packs or forks. |
container | You want a kernel per sandbox from ordinary OCI images, on macOS 26. |
docker or podman | You already run one, or you are on Linux without KVM. The sandboxes share a kernel. |
| a container backend | You start many short-lived sandboxes and start-up time matters most. One starts 1 to 2 s sooner. |
boxer's threat model is a careless agent. If containment is why you use boxer, keep a kernel-per-sandbox backend.
Every backend passes the smoke suite. On docker, podman and Apple container, the 9 tests for smolvm-only features are skipped. This holds on macOS, and on Linux where the backend exists
(results).
Set the backend
backend = "docker"
[network]
mode = "on" # or "off"Check the result:
boxer doctor # the backend in use, with its boundary
boxer backends # every installed backend comparedInstall a backend
smolvm
curl -sSL https://smolmachines.com/install.sh | bash
smolvm --versionOn Linux, smolvm needs a /dev/kvm your user can open. Run sudo usermod -aG kvm $USER, then log
in again. Inside a Linux VM, that VM needs nested virtualisation.
Apple container
Requires macOS 26 on Apple Silicon.
brew install container
container system start
container system kernel set --recommendedbackend = "container"
[network]
mode = "on"docker
Install Docker Desktop or OrbStack on macOS, or the docker engine on Linux.
backend = "docker"
[network]
mode = "on"podman
Install with brew install podman, or your distribution's package. On macOS, create and start a
machine with the applehv provider:
export CONTAINERS_MACHINE_PROVIDER=applehv
podman machine init && podman machine startbackend = "podman"
[network]
mode = "on"Start-up time
Time for a Next.js dev server to answer HTTP 200. Measured one backend at a time on a wiped host, three samples each, 2 vCPU and 4 GB per sandbox, on a 10-core Apple Silicon Mac (2026-09-28):
| backend | one worktree | three worktrees at once |
|---|---|---|
docker (OrbStack) | 4.3 s | 10.9 s |
container | 5.3 s | 14.4 s |
smolvm | 6.4 s | 15.6 s |
- A kernel per sandbox costs smolvm and
container1 to 2 s at one worktree and 3.5 to 4.7 s at three. - Podman was not measured on 2026-09-28. Benchmarks has its 2026-09-25 figures.
- A warm
boxer runon smolvm takes 33 ms.docker execinto a running container takes 29 ms. - Memory is not compared. See Benchmarks.
The benchmarks have the method and caveats.
Known runtime problems
boxer backends checks for the ones it can detect. boxer backends --probe creates, uses and
deletes a real sandbox on each backend.
| problem | what happens | what to do |
|---|---|---|
podman on the libkrun provider | It cannot bind-mount host paths, so no worktree can be mounted. A machine created under one provider is invisible to podman machine commands run under another. | Use the applehv provider on a Mac: CONTAINERS_MACHINE_PROVIDER=applehv podman machine init. |
Apple container without its service or kernel | Every create fails. | Run container system start, then container system kernel set --recommended. |
| docker on Ubuntu, with AppArmor | A process started by one docker exec cannot signal one started by another, even as root, because AppArmor stacks each exec's profile. boxer restart cannot stop a service with a signal there. | Nothing: boxer restarts the container instead, which keeps its files. A kill you run inside the sandbox is refused the same way. |
| podman storage errors | After heavy use, overlay mounts fail with input/output error, from the Mac client and inside the VM. | Run podman machine stop && podman machine start. This cleared it without losing anything. |
boxer handles these itself:
- OrbStack stale mounts. OrbStack can give a new container a stale mount when the same worktree path was removed and added again moments earlier. Orchestrators do this when they reuse a task name. boxer writes through the mount before running anything, and recreates the sandbox once if that fails.
- SELinux. On Fedora, RHEL and CoreOS, SELinux denies an unlabelled bind mount and the guest
sees
Permission deniedon/workspace. When SELinux is enforcing on a Linux host, boxer adds the shared relabel (:z) to every mount on docker and podman. This relabels the worktree and the package caches as container-shared on the host. boxer never does this on macOS. - Rootless podman. Rootless podman maps you to the container's root. A non-root
user, such as a devcontainer'sremoteUser, then sees the worktree as root's and cannot write it. boxer reads that user's uid from the image and recreates the sandbox once with--userns=keep-id. Files the user writes are owned by you on the host. - Runtime errors. "No such container" exits 1 and "OCI runtime exec failed" exits 127. boxer reads the runtime's message and reports a boxer error instead. A command that never ran is not reported as a failed command.
Not supported
| backend | why |
|---|---|
| Firecracker | It needs /dev/kvm, which macOS does not have. Its maintainers declined a Virtualization.framework port. |
Docker Sandboxes (sbx) | It has a per-host allowlist, but no labels on any object and no way to inspect a sandbox. boxer cannot prove which sandboxes it owns. |
| Remote backends, such as Vercel Sandbox | They copy the workspace instead of mounting it. No boxer backend copies a workspace yet. |