Skip to main content
A machine is somewhere you already have: a container, a microVM, a cloud sandbox or a host over SSH. Declare each one under machines: with the provider that reaches it, and run captured programs there with the machine runtime, whose machine names it. You create and stop the machine; Mirage only runs programs on it. captures defaults to @external, every program Mirage does not have itself.
A line or a command can also name its machine: execute(machine=), a machine on the shell request, or a policy’s Route(machine=). A program there opens workspace files at the same paths, so the machine must see the mounts. docker, e2b, ssh, smolvm, microsandbox, and apple_container pick how with mount_type: ssh serves them from the workspace, fuse from a Mirage inside the machine, and both mixes the two. Daytona uses a Mirage inside. Sandlock is a runtime, not a machine: it runs programs on this Linux host, confined, with a FUSE folder you grant. After following a provider page’s Connecting the workspace steps, run the shared workspace file check to confirm reads and writes cross the runtime boundary.

Language compatibility

Python modules, file modes and other language APIs come from the interpreter installed on the machine. Mirage forwards the captured program’s arguments, input, environment and working directory; a working directory the machine lacks fails the program with exit 126. Each run also gets MIRAGE_PROCESS, the id of its Mirage process (w1:9), which every process it starts inherits. Each provider’s Runtime behavior table describes that boundary. File operations reach workspace mounts through the connection configured for that provider. They also remain subject to the machine’s own permissions and confinement.

Processes and signals

ps lists a program on a machine with the machine and its pid there, and everything the program started as processes of their own, found by a scan of the machine’s /proc for its MIRAGE_PROCESS:
ps -o machine,mpid selects those columns, and ps f or ps --forest draws each command under its parent’s. A process a scan found leaves the list once a scan no longer sees it. kill sends a signal to a program on a machine as you sent it: a program that traps USR1 gets it and runs on, and one that TERM ends exits with its own status, which wait reports. KILL is sent only when you ask for it (kill -9, timeout -k) or when Mirage shuts the workspace down. Mirage’s own stops, a head that has read enough, a line’s timeout, a cancelled line, send TERM, then KILL a second later, to every process on the machine that carries the run’s MIRAGE_PROCESS. In a Mirage shell a pid is Mirage’s: they start at 4194305, past any machine’s own. A smaller number given to kill, kill -0, wait or ps -p is a machine pid, the process whose program has that pid on its machine; one that two machines both have is refused as an ambiguous job spec. Inside a program on a machine a pid is that machine’s, and a line sent whole to a machine (@line) runs in that machine’s shell, with its pids. Every one of these follows the session’s processes grants.

Machine states

Each check finds a machine in one of three states: GET /v1/workspaces/{id}/machines checks every machine and lists the host first; GET /v1/workspaces/{id}/machines/{machine} checks one and lists its processes. mirage workspace list-machines prints the same table.

The machine image

Every machine needs a POSIX shell at /bin/sh, which starts each command, and grep, which finds what a run cut short left running. With libfaketime in the image, programs see the workspace’s system_time; without it they see the machine’s clock. With mount_type: ssh the machine also needs sshfs. A machine that serves the mounts with its own Mirage needs fuse3 and Mirage. The repository’s Dockerfile builds that image from the current checkout, with every mountable backend:
Narrow it to the backends you use with MIRAGE_EXTRAS:
The same image serves every provider: run it under Docker, Apple’s container, Microsandbox or smolvm, or use it as a Daytona image or an E2B template base. Add the programs your captures need, such as Node.js for node. The Mirage inside is the Python build, whichever SDK the host uses.

Timeouts

command_limits apply to a program on a machine as to any command: a line past its @line entry’s timeout_seconds exits 124, and max_bytes and max_lines cap the program’s output.
A timeout stops the program: Mirage sends TERM, then KILL a second later, to every process there that carries the run’s MIRAGE_PROCESS; that needs /bin/sh, tr, grep and /proc on the machine, and Mirage gives up after 5 seconds if it stops answering. In TypeScript, a Daytona command keeps running after a timeout. A Daytona sandbox has no timeout of its own, so it keeps running and billing. Use the provider’s own limits.

Serving session files

With mount_type: ssh, workspace file access follows the session’s permissions and policies. You do not need to expose a public file server or copy service credentials into the machine. Each machine is assigned to one session. Mirage refuses to run a program when it cannot enforce the session’s file restrictions. Unsupported cases include:
  • Restrictions on / or /dev.
  • Hiding a mount when doing so would expose the machine’s own files underneath.
  • Restrictions on mounts served by a separate Mirage inside the machine.
  • File rules that depend on which program opens a file.