Sandboxes
You create sandboxes at runtime with E2B.CreateSandbox. Pass a template or snapshot to create from it, or nothing and pass templateID per call.
// in the Worker's constructionconst sandboxes = yield* E2B.CreateSandbox(Agent);Provide E2B.CreateSandboxHttp on the Worker. The Worker gets its own E2B API key as a secret, revoked when the Worker is destroyed.
One sandbox per request
Section titled “One sandbox per request”// in fetchconst sandbox = yield* Effect.acquireRelease(sandboxes.create({ timeout: 120 }), (sandbox) => Effect.ignore(sandbox.kill()),);The sandbox is killed when the request’s scope closes. timeout is in seconds; E2B kills the sandbox when it runs out.
A long-lived sandbox per user
Section titled “A long-lived sandbox per user”const [existing] = yield* sandboxes.list({ metadata: { user }, state: ["running", "paused"] });const sandbox = existing ? yield* sandboxes.connect(existing.sandboxID, { timeout: 600 }) : yield* sandboxes.create({ metadata: { user }, autoPause: true, timeout: 600 });With autoPause, E2B pauses the sandbox instead of killing it when the timeout runs out, memory included. connect resumes it.
A sandbox the stack owns
Section titled “A sandbox the stack owns”import * as E2B from "@alchemy.run/e2b";import { Agent } from "./Agent.ts";
export const DevBox = E2B.Sandbox("DevBox", { template: Agent, timeout: "10 minutes" });E2B.Sandbox declares one sandbox that lives as long as the stack. It runs for timeout after it starts or resumes, then E2B pauses it with its memory and files. The next request to it resumes it. A rebuilt Agent replaces it.
// in the Worker's constructionconst devBox = yield* E2B.ConnectSandbox(DevBox);// in fetchconst result = yield* devBox.exec("git pull && npm test", { cwd: "/home/user/repo" });ConnectSandbox returns the same handle as create, with sandboxId read at runtime. Provide E2B.ConnectSandboxHttp on the Worker.
With autoResume: false, calls into a paused sandbox fail with SandboxNotRunning until you call devBox.resume().
Run commands
Section titled “Run commands”const result = yield* sandbox.exec("npm test", { cwd: "/home/user/app" });// { stdout, stderr, exitCode, pid, error }A non-zero exit code is returned, not failed. exec(["node", "--version"]) runs an argument vector without a shell. stdin feeds the command, and timeout kills it and fails with E2B.ExecTimedOut.
Stream output
Section titled “Stream output”const build = yield* sandbox.start("npm run build");yield* build.output.pipe(Stream.runForEach((chunk) => Effect.log(chunk.fd, chunk.data.length)));const { exitCode } = yield* build.wait;start returns a Process with its output, write, kill and wait. Closing the scope detaches; the process keeps running. Re-attach with sandbox.processes.attach({ pid }).
yield* sandbox.files.write("/home/user/app/config.json", JSON.stringify(config));const log = yield* sandbox.files.readText("/home/user/app/out.log");const entries = yield* sandbox.files.list("/home/user/app");files.watch(path) streams changes under a directory.
Pseudo-terminals
Section titled “Pseudo-terminals”const pty = yield* sandbox.pty.open({ cols: 120, rows: 40 });yield* pty.write("ls\n");yield* pty.resize({ cols: 160, rows: 48 });pty.output streams the terminal’s bytes. Closing the scope detaches.
const port = yield* sandbox.getTcpPort(3000);const res = yield* port.fetch(HttpClientRequest.get("/health"));port.url; // https://3000-<sandboxId>.e2b.appport.fetch also takes the request a Worker received, and proxies it to the port:
const port = yield* sandbox.getTcpPort(3000);return yield* port.fetch(yield* HttpServerRequest);Lifecycle
Section titled “Lifecycle”yield* sandbox.setTimeout("30 minutes");yield* sandbox.pause();yield* sandbox.kill();Clean up
Section titled “Clean up”Objects created at run time are tagged with the stack, stage and binding (alchemy::stack, alchemy::stage, alchemy::id) and are not deleted when the stack is destroyed. List them by those tags to clean them up.
const left = yield* sandboxes.list({ metadata: { "alchemy::stack": "my-app", "alchemy::stage": "prod" }, state: ["running", "paused"],});yield* Effect.forEach(left, (sandbox) => sandboxes.kill(sandbox.sandboxID));A template can’t be deleted while sandboxes run on it, so kill them before you destroy the stack.