boxer

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 | podman

BOXER_BACKEND overrides it for one command.

Compare backends

backenda sandbox iskernelegress allowlistpacksforkhost edits reach a dev server
smolvm (default)a microVMone per sandboxyesyesyesthrough polling, which boxer turns on
container (Apple, macOS 26 on Apple Silicon)a lightweight VM per containerone per sandboxnononothrough polling, which boxer turns on
dockera containershared by every sandbox on the machinenononoyes with OrbStack; Docker Desktop not measured
podmana containershared by every sandbox on the machinenononoon 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. Set network.mode to "on" or "off" for them.
  • Packs. smolvm caches each environment pack, so image_setup runs once per host. On the other backends it runs once per sandbox.
  • Fork. boxer fork needs 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 container and podman's Mac VM, most watchers miss edits you make on your machine, so boxer sets WATCHPACK_POLLING and CHOKIDAR_USEPOLLING in the sandbox. Next.js's Turbopack has no polling that works there: run next 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

usewhen
smolvmThe default. You want the egress allowlist, packs or forks.
containerYou want a kernel per sandbox from ordinary OCI images, on macOS 26.
docker or podmanYou already run one, or you are on Linux without KVM. The sandboxes share a kernel.
a container backendYou 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 compared

Install a backend

smolvm

curl -sSL https://smolmachines.com/install.sh | bash
smolvm --version

On 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 --recommended
backend = "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 start
backend = "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):

backendone worktreethree worktrees at once
docker (OrbStack)4.3 s10.9 s
container5.3 s14.4 s
smolvm6.4 s15.6 s
  • A kernel per sandbox costs smolvm and container 1 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 run on smolvm takes 33 ms. docker exec into 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.

problemwhat happenswhat to do
podman on the libkrun providerIt 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 kernelEvery create fails.Run container system start, then container system kernel set --recommended.
docker on Ubuntu, with AppArmorA 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 errorsAfter 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 denied on /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's remoteUser, 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

backendwhy
FirecrackerIt 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 SandboxThey copy the workspace instead of mounting it. No boxer backend copies a workspace yet.

On this page