pyodide and monty runtimes run python3 in-process on your 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.
Six sandbox runtimes ship from @struktoai/mirage-node, all with the same
surface:
docker, apple_container 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 Docker container shares its host’s kernel, an
Apple Container and a smolvm microVM each boot their own, and an ssh machine
is whatever machine you point it at.
Looking for Sandlock? Mirage’s
Sandlock adapter is available in
@struktoai/mirage-node on Linux.
It runs native programs, including Python and Node.js.Routing: what goes to the sandbox
Sandbox runtimes default tocaptures: ["@external"]: only program names
Mirage does not resolve are delegated. Existing Mirage commands, pipes and
redirects stay in the workspace.
captures: ["python3", "node", "@external"] selects native interpreters and
keeps the external fallback. Without those named captures, Mirage’s
interpreter commands keep their normal runtime bindings.
Shell builtins such as cd, export, and echo stay in Mirage even when
named in captures.
A named capture is a program: which gcc prints /usr/bin/gcc, and that
file’s comment names the runtime it runs on. The @external fallback takes
any word, so a name only the fallback would run has no file there: which,
command -v and type report it missing, as bash does for a name its
command_not_found_handle takes, and running it still delegates.
python3 job.py | grep error > /logs/errors delegates only python3;
Mirage executes grep and writes the VFS output. An explicit captures: ["*"]
opts into delegating the entire shell line.
The SDK exports the same marker:
python3 job.py *.csv runs python3 job.py a.csv b.csv, and a
quoted or escaped glob stays literal. Command rules then judge that expanded
argv: grep *.txt is refused when a match that grep reads as a file is protected.
An interpreter’s script is a file operand like any other, so python3 job.py is
judged on job.py however it is spelled, and the words after it stay its argv.
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 in-sandbox mount is
served by the Python Mirage build, so the image is the same for both
language SDKs.
The
ssh provider can skip all of this when the files live on the ssh
machine itself: mount them over the ssh VFS 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:
The shared image uses the Python Mirage CLI to serve workspace mounts
inside the sandbox. Your host application can use either SDK, including
TypeScript; it communicates with the sandbox through the runtime’s
transport. The image’s implementation and the host SDK are independent.When building a custom image, install the programs your captured commands
need (for example, Node.js for
node) and serve workspace mounts at the
same paths as the host workspace.container (build it with container build, same
flags) or smolvm, use it as a Daytona 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 workspace 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 timeoutSeconds answers exit 124; maxBytes and maxLines
cap its output the same way. There is no sandbox-specific limit surface.
SDK loader overrides
Runtime subclasses that overrideloadSdk() import the SDK type from
the provider’s sdk module:
runtime modules
export the runtime classes.
Evaluating route-policy scripts
A sandbox runs programs; it does not evaluate expressions, so it cannot run route-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 -.