# We Reverse-Engineered Docker Sandbox's Undocumented MicroVM API

Rivet (rivet.dev) reverse-engineered the undocumented HTTP API behind Docker Sandboxes' microVM mode and built an open-source SDK on top — the [[toolbox/sandbox-agent-sdk]]. The post documents the API surface, shows how to create and talk to a microVM from a shell, and frames the bet: this might be the unified microVM-management surface that Docker did for containers a decade ago.

This is the operational follow-up to [[microvm-2026]], which named *Docker Sandboxes* in the platform survey but couldn't yet describe the API.

## What Docker Sandboxes actually is

`docker sandbox run claude ~/project` looks like `docker run` but uses **completely different technology** underneath. Each invocation spins up a microVM with a separate kernel — not a container in the host kernel — so an AI coding agent can be handed `--dangerously-skip-permissions` without putting the host at risk. See [[microvm]] for the architectural rationale and [[matryoshka-isolation]] for the dominant containers-in-VMs sandwich pattern.

The CLI is restricted to Docker's six whitelisted agents (Claude, Codex, Gemini, Copilot, Kiro, Cagent). Everything else has to go through the undocumented API.

| | Docker Container | Docker Sandbox |
|---|---|---|
| Security | Shared kernel (namespaces) | Separate kernel (microVM) |
| Untrusted code | Not safe | Safe |
| Network access | Direct HTTP | Via filtering proxy |
| Volumes | Direct mount | Bidirectional file sync |
| Platform | Linux, macOS, Windows | macOS, Windows only |

Linux isn't supported because Docker Desktop uses platform-specific virtualization there (Apple Virtualization.framework on macOS, Hyper-V on Windows). Nested virtualization is required. Docker Desktop 4.58+.

## The `/vm` API surface

`sandboxd` listens on `~/.docker/sandboxes/sandboxd.sock` with three endpoints:

- `GET /vm` — list all VMs
- `POST /vm` — create a VM
- `DELETE /vm/{vm_name}` — destroy a VM

Create:

```
curl -X POST --unix-socket ~/.docker/sandboxes/sandboxd.sock \
  http://localhost/vm \
  -H "Content-Type: application/json" \
  -d '{"agent_name": "my-sandbox", "workspace_dir": "/path/to/project"}'
```

Response includes `vm_config.socketPath`, `fileSharingDirectories`, `stateDir`, and `ca_cert_path` (the MITM proxy CA). VM name follows `{agent_name}-vm`.

## The clever isolation move

The crucial structural difference from regular Docker: **each microVM gets its own Docker daemon** at `~/.docker/sandboxes/vm/<name>/docker.sock`. The usual `/var/run/docker.sock` model is shared — anyone with socket access controls every container. Sandboxes invert that: containers run normally *inside* the microVM and are completely isolated from the host and from other VMs.

Once you have the per-VM socket, the rest is just plain Docker:

```bash
# Build on host
docker build -t my-image:latest .
# Archive image
docker save my-image:latest > /tmp/image.tar
# Load into microVM
docker --host "unix://$VM_SOCK" load < /tmp/image.tar
# Run inside
docker --host "unix://$VM_SOCK" run -d my-image:latest
```

## Networking is a forced MITM proxy

Outbound traffic from inside the microVM routes through a filtering proxy at `host.docker.internal:3128`. The proxy does **man-in-the-middle on HTTPS** for policy enforcement — meaning containers either trust the proxy's CA (`ca_cert_path` from the create response) or set `NODE_TLS_REJECT_UNAUTHORIZED=0` for prototyping. This is essentially [[sandboxing-ai-agents]] Layer 3 (HTTP policy) baked into the substrate; you can't run containers inside a sandbox without going through it.

Volumes work because the workspace syncs at the *same absolute path* on both sides — so a host bind mount `/Users/me/project:/Users/me/project` just works.

## Why this matters

Three observations from the writeup, all of which slot into existing wiki framings:

1. **Docker is building the unified microVM surface.** Just like containers in the 2013-2015 era, microVM tooling today is fragmented across Firecracker, Cloud Hypervisor, Kata, libkrun, and platform-specific options. Docker shipping a CLI + HTTP API behind one consistent socket is the same play that turned LXC plumbing into the developer-facing `docker run`. See [[microvm-2026]] for the "this is microVMs' Docker moment" thesis the writeup is implicitly betting on.
2. **The whitelist is the constraint.** The undocumented API is the entire system minus the agent-name allowlist — exactly the gap a third-party SDK needs to fill. Rivet's Sandbox Agent SDK does that; expect others if the API survives.
3. **macOS/Windows-only matters.** The platform restriction makes this concretely a developer-laptop primitive, not a server-side one — at least until Docker Desktop's Linux story changes. For production sandboxing on Linux today, the choices in [[microvm-2026]] (Firecracker, Cloud Hypervisor, Kata) still hold.

## Stability caveat

The API is undocumented and subject to change. Rivet's writeup acknowledges this directly — it works today on Docker Desktop 4.58+, and that's the entire warranty. Any tooling built on this lives at the mercy of an internal-only surface that Docker has every right to break in the next minor release. This is the structural inverse of [[abi-stability]] (Win32 as the durable ABI on Linux) — interesting to track as a counter-example of what happens when the upstream *isn't* committed to the contract.

## See also

- [[microvm-2026]] — the broader microVM-for-agent-sandboxing survey (Docker Sandboxes mentioned at the platform level; this page is the API-level zoom-in)
- [[microvm]] — architectural concept
- [[matryoshka-isolation]] — containers-in-VMs sandwich pattern
- [[sandboxing-ai-agents]] — four-layer taxonomy; the MITM proxy is Layer 3 baked in
- [[toolbox/sandbox-agent-sdk]] — Rivet's SDK on top of this API
- [[rivet-blog]] — author entity
- [[toolbox/crabtrap]] — Brex's LLM-as-judge HTTP proxy; the same Layer-3 idea applied to traffic Docker's proxy isn't filtering on
- [[toolbox/superhq]], [[hazmat]], [[bubblewrap-dev-env]] — alternative substrate choices
