monty, wasi, sandlock, and local runtimes run python3 on your
own machine. A sandbox runtime does the opposite: it takes a whole
command line and runs it inside a separate machine, a local container or
microVM or a cloud sandbox. Reach for it when a line needs a real OS, heavy
packages, or a GPU that the in-process interpreters cannot give it.
Five sandbox runtimes ship today, all with the same surface:
docker and smolvm run on your own machine, Daytona and e2b in the
cloud, and ssh reaches anything you can already ssh into. The isolation
differs too: a container shares your kernel, a smolvm microVM boots its
own, and an ssh machine is whatever machine you point it at.
Sandlock is listed in this sidebar
because it isolates execution, but it is not the same runtime tier. It
confines only the host
python3 interpreter; the four providers above run
arbitrary captured command lines in another container, VM, or machine.Routing: what goes to the sandbox
A sandbox runtime does not claim every line. Each runtime captures a set of commands; a captured line runs in the sandbox, everything else stays on the local vfs. You list the runtimes in order, andvfs is the catch-all:
grep, cat, and ls run locally as always, while python3 train.py
runs on the cloud box. Mirage never creates, provisions, or deletes
sandboxes: you bring your own (a running container or microVM, or a live
Daytona or E2B sandbox), the first captured line connects to it, and every
captured line execs inside it verbatim.
The workspace inside the sandbox
For the job to see your mounts as ordinary files, the sandbox must serve the workspace itself, and provisioning that is yours, like everything else about the sandbox. Run Mirage inside it, with the same mounts at the same prefixes as the host workspace, each FUSE-mounted at its own prefix:/data backed by S3
is a real directory. Because you write the sandbox-side config, it can
differ from the host’s where it should: the endpoint that is
127.0.0.1:9000 on your laptop is host.docker.internal:9000 from inside
a container, credentials can be scoped down, and none of it ever travels
over the provider’s exec API. The flip side is that keeping the prefixes
in step with the host workspace is your job; a sandbox serving different
mounts fails loud only when a path misses.
This needs an image with fuse3 and Mirage plus your backends installed
(see The sandbox image).
The
ssh provider can skip all of this when the files live on the ssh
machine itself: mount them over the ssh resource at a prefix equal to
their remote absolute path, and captured lines open them natively with no
FUSE and no remote Mirage. See
Skip the FUSE on the SSH page.Paths inside the sandbox
Mirage rewrites nothing: the line, its cwd, and every path in it pass through verbatim. With the sandbox serving the same prefixes,cd /data
then python3 train.py works, and so does an absolute path like
python3 /data/train.py, because /data means the same thing on both
sides.
The sandbox image
The sandbox needsfuse3 plus Mirage with the backends you mount. The repo
ships a Dockerfile that builds this image from the current checkout, so it
always matches your code:
sandbox extra, every mountable backend plus
fuse. Narrow it to just the backends you use for a smaller image, or extend
it as a base:
image/snapshot source, or use
it as an E2B template base. Continue with the provider page in the Sandbox
sidebar for connection and configuration details.
Selecting in YAML
Sandbox runtimes are ordinaryruntimes entries: a name, its captures, and
a config block describing the machine (mirroring a mount’s config
block), with vfs as the in-process catch-all. Only the selected runtime
consumes its entry, so one file stays portable:
Resource limits
A captured line is a command like any other: the samecommand_limits
that guard cat or grep guard python3, including in the sandbox. A run
that exceeds timeout_seconds answers exit 124; max_bytes and max_lines
cap its output the same way. There is no sandbox-specific limit surface.
Serving as the policy engine
A sandbox runs whole lines; it does not evaluate expressions, so it cannot run policy scripts. To make one eligible, subclass it and implementeval with your own transport. The
docker eval examples
do exactly that by piping a small harness to the container’s python3 -.