Skip to content

Sandboxes

A sandbox is a Firecracker microVM that stays around between calls. Its root filesystem is a disk of its own, so files and installed packages survive a stop. It can mount other disks, serve ports, and run for up to 24 hours per session. After 30 minutes without a connection it pauses, keeping its memory and processes, and the next connection resumes it.

runningbilledpausedmemory keptstoppeddisk keptdeletedidle · pause()connect · resume()stop()boot()delete
A paused sandbox keeps its memory and running processes. A stopped one keeps only its disks. Neither is billed for compute.

Declare a sandbox in your Stack to keep it across deploys. Each mounts entry names a disk and a path:

export const Workspace = Archil.Disk("Workspace", {});
export const DevBox = Archil.Sandbox("DevBox", {
baseImage: "node:24-bookworm",
mounts: [{ disk: Workspace, path: "/workspace" }],
ports: [3000],
});

A mount change updates the same sandbox: it stops, then starts with the new mounts.

An entry takes Archil’s own options: mode (exclusive, conditional, or read-only), subdirectory, and queueMs:

export const Box = Archil.Sandbox("Box", {
mounts: [
{ disk: Workspace, path: "/workspace", mode: "conditional" },
{ disk: Dataset, path: "/data", mode: "read-only", subdirectory: "exports" },
],
});

exclusive (the default) lets one client write the whole disk. conditional lets several write at once. Branches and the shared mode belong to the FUSE client, so they work in mounts on your own hosts, not in a sandbox.

Bind an image, then create a sandbox per task. A mount’s disk is a handle, such as one from CreateDisk:

export const Agent = Archil.Image("Agent", { image: "node:24-bookworm" });
const sandboxes = yield* Archil.CreateSandbox(Agent);
const sandbox = yield* sandboxes.create({
name: "task-1",
mounts: [{ disk: home, path: "/home/user" }],
idleTtlSeconds: 300,
});

exec runs a command to completion. A non-zero exit comes back in exitCode:

const result = yield* sandbox.exec("npm test", { cwd: "/home/user/app" });
const upper = yield* sandbox.exec(["tr", "a-z", "A-Z"], { stdin: "hello" });

start returns a process whose output you can stream. Closing the scope detaches; the process keeps running, and attach picks it up again by id:

const server = yield* sandbox.start("npm run dev", { cwd: "/home/user/app" });
yield* server.output.pipe(Stream.take(10), Stream.runDrain);

pty.open runs a shell on a pseudo-terminal:

const pty = yield* sandbox.pty.open({ cols: 120, rows: 40 });
yield* pty.write("ls -la\n");
yield* pty.resize({ cols: 160, rows: 50 });
yield* sandbox.files.write("/home/user/app/.env", "MODE=test");
const env = yield* sandbox.files.readText("/home/user/app/.env");

Archil’s process API moves whole files. List, remove or create directories with exec.

yield* sandbox.pause();
yield* sandbox.resume();
const copy = yield* sandbox.fork({ name: "task-1-retry" });

A fork copies the sandbox’s memory and disk into a new one. Archil only forks a sandbox with mounts while it is stopped. stop() and boot() stop and start it.

A declared sandbox’s ports, or exposePort on any sandbox, serves a port at its own https://<port>-<id>.<region>.archil.app hostname. getTcpPort returns that port, and its fetch sends a request to it:

yield* sandbox.exposePort(3000);
const port = yield* sandbox.getTcpPort(3000);
const response = yield* port.fetch(HttpClientRequest.get("/health"));

Pass fetch the request a handler received, and it proxies the method, path, query, headers and body, then returns the response:

return yield* port.fetch(yield* HttpServerRequest);

Anyone with the URL can call a public port. Give a browser a port token instead, which it sends in the X-Archil-Token header:

const token = yield* sandbox.createPortToken(3000, "1h");

ConnectSandbox returns the same handle for a sandbox in your Stack:

const devbox = yield* Archil.ConnectSandbox(DevBox);
yield* devbox.exec("pnpm build", { cwd: "/workspace" });

A sandbox boots node:24-bookworm unless you give it a baseImage (any public linux/amd64 image) or an image from Archil.Image, for private registries and Dockerfiles.

Give a sandbox a main and it runs your program at a public URL. See Programs.