Skip to content

Modal.Sandbox reference

Source: packages/modal/src/ConnectSandbox.ts Kind: Layer · Provides: Modal.ConnectSandbox

ConnectSandbox over Modal’s API. Grants the runtime’s identity the contributor role on the box’s environment.

Source: packages/modal/src/CreateSandbox.ts

Create and drive Modal Sandboxes from any runtime: a Worker, a Lambda, a Durable Object, or a script.

Bind it to an App (sandboxes boot from the image you pass to create) or to an Image (it is the default image, and sandboxes go in the image’s App). A sandbox lives at most 24 hours; to keep work across sandboxes, use a Volume, or snapshot it (snapshotFilesystem, snapshotMemory) and start the next one from the snapshot.

Sandboxes are runtime objects: they aren’t in Alchemy state. Stopping the App (destroying the Modal.App resource) terminates every sandbox in it.

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: sandboxes.list({ tags: { "alchemy::stack": "my-app" } }), or Modal’s SandboxList with tags. Your own tags merge under the three. A persistent sandbox takes neither tags nor a name, so it carries no owner; it lives in the App.

CreateSandbox: Create a sandbox per request

Section titled “CreateSandbox: Create a sandbox per request”
import * as Cloudflare from "@alchemy.run/cloudflare";
import * as Modal from "@alchemy.run/modal";
import * as Effect from "effect/Effect";
import * as HttpServerResponse from "effect/http/HttpServerResponse";
import { Warm } from "./Coder.ts";
export default class Api extends Cloudflare.Worker<Api>()(
"Api",
{ main: import.meta.url },
Effect.gen(function* () {
const sandboxes = yield* Modal.CreateSandbox(Warm);
return {
fetch: Effect.gen(function* () {
const sandbox = yield* Effect.acquireRelease(
sandboxes.create({ timeout: "10 minutes" }),
(sandbox) => Effect.ignore(sandbox.terminate()),
);
const test = yield* sandbox.exec(["pnpm", "test"], { cwd: "/work" });
return yield* HttpServerResponse.json(test);
}).pipe(Effect.scoped, Effect.orDie),
};
}).pipe(Effect.provide(Modal.CreateSandboxHttp)),
) {}
const sandbox =
(yield* sandboxes.fromName(`session-${user}`)) ??
(yield* sandboxes.create({ name: `session-${user}`, idleTimeout: "10 minutes" }));

CreateSandbox: Secrets the sandbox never sees

Section titled “CreateSandbox: Secrets the sandbox never sees”
// construction phase: read the Secret's id
const openaiId = yield* (yield* OpenAI).secretId;
// in a handler
const openai = { secretId: yield* openaiId };
const sandbox = yield* sandboxes.create({
outboundPolicy: {
routes: [
{
https: {
domains: ["api.openai.com"],
headerReplacements: [{ secret: openai, headers: { Authorization: "Bearer $OPENAI_API_KEY" } }],
},
},
{ any: { redirect: "block" } },
],
},
});

CreateSandbox: A cloud identity, not a key

Section titled “CreateSandbox: A cloud identity, not a key”
const sandbox = yield* sandboxes.create({
image: "amazon/aws-cli",
includeOidcIdentityToken: true,
});
// MODAL_IDENTITY_TOKEN is an OIDC token from https://oidc.modal.com
const role = yield* sandbox.exec(
`aws sts assume-role-with-web-identity --role-arn ${roleArn} --role-session-name sandbox --web-identity-token "$MODAL_IDENTITY_TOKEN"`,
);
const box = yield* sandboxes.createPersistent({ image: "node:22-bookworm" });
const session = yield* box.session();
yield* session.exec("echo hi >> /root/notes.txt");
// later, from any request: resumes the box if it paused
const again = yield* (yield* sandboxes.persistent(box.persistentSandboxId)).session();
yield* sandbox.exec(["pnpm", "install"], { cwd: "/work" });
const image = yield* sandbox.snapshotFilesystem();
const next = yield* sandboxes.create({ image });

Source: packages/modal/src/CreateSandboxHttp.ts Kind: Layer · Provides: Modal.CreateSandbox

CreateSandbox over Modal’s API. Grants the host’s identity (a scoped Modal.ServiceUser) the contributor role on the App’s environment.

Source: packages/modal/src/PersistentSandbox.ts

A Modal persistent sandbox: one box whose filesystem outlives its sessions. Each session is a regular sandbox that runs up to an hour and pauses after five idle minutes; the next session() resumes it from the last session’s filesystem. Use it for a long-lived workspace, such as a shared dev box or an agent’s home, that the Stack owns.

Modal’s persistent sandboxes are experimental: they don’t take a name, GPUs, env, tags or memory snapshots yet. Changing any prop replaces the box (and its files).

import * as Modal from "@alchemy.run/modal";
import { Coder, Warm } from "./Coder.ts";
export const DevBox = Modal.PersistentSandbox("DevBox", { app: Coder, image: Warm, workdir: "/work" });
// construction phase
const sandboxes = yield* Modal.CreateSandbox(Coder);
const devBoxId = yield* (yield* DevBox).persistentSandboxId;
// in fetch:
const session = yield* (yield* sandboxes.persistent(yield* devBoxId)).session();
const result = yield* session.exec(["git", "status"]);

Source: packages/modal/src/Sandbox.ts

An Effect program that runs in Modal sandboxes. Alchemy bundles main, builds it into an image on Modal’s builder (no Docker), and stores its env, including the credentials its bindings need, in a Modal Secret. Modal.CreateSandbox(Agent) then boots sandboxes that run the program and serve its fetch handler; sandbox.getTcpPort() reaches it.

Each sandbox is an isolated instance: give one to every user, session or task, and terminate it when the work is done. A sandbox lives at most 24 hours. Bindings work inside it like on any host, so the program can read a Modal Volume or a Cloudflare R2 bucket.

src/Agent.ts
import * as Modal from "@alchemy.run/modal";
import * as Effect from "effect/Effect";
import * as HttpServerResponse from "effect/http/HttpServerResponse";
import { Coder } from "./Coder.ts";
export default class Agent extends Modal.Sandbox<Agent>()(
"Agent",
{ main: import.meta.url, app: Coder },
Effect.gen(function* () {
return {
fetch: Effect.succeed(HttpServerResponse.text("ready")),
};
}),
) {}
src/Agent.ts
import * as Modal from "@alchemy.run/modal";
import { Coder } from "./Coder.ts";
export const Agent = Modal.Sandbox("Agent", {
main: "./src/agent-server.ts",
app: Coder,
env: { GREETING: "hello" },
});
export type AgentEnv = Modal.InferEnv<typeof Agent>;
src/agent-server.ts
import { createServer } from "node:http";
import type { AgentEnv } from "./Agent.ts";
const env = process.env as unknown as AgentEnv;
createServer((_request, response) => response.end(env.GREETING)).listen(Number(process.env.PORT));
src/Api.ts
import * as Cloudflare from "@alchemy.run/cloudflare";
import * as Modal from "@alchemy.run/modal";
import * as Effect from "effect/Effect";
import * as HttpClientRequest from "effect/http/HttpClientRequest";
import * as HttpServerResponse from "effect/http/HttpServerResponse";
import Agent from "./Agent.ts";
export default class Api extends Cloudflare.Worker<Api>()(
"Api",
{ main: import.meta.url, compatibility: { flags: ["nodejs_compat"] } },
Effect.gen(function* () {
const agents = yield* Modal.CreateSandbox(Agent);
return {
fetch: Effect.gen(function* () {
const agent = yield* agents.create({ timeout: "10 minutes" });
const port = yield* agent.getTcpPort();
const response = yield* port.fetch(HttpClientRequest.get("/"));
const text = yield* response.text;
yield* agent.terminate();
return HttpServerResponse.text(text);
}).pipe(Effect.orDie),
};
}).pipe(Effect.provide(Modal.CreateSandboxHttp)),
) {}
src/Agent.ts
import * as Modal from "@alchemy.run/modal";
import * as Effect from "effect/Effect";
import * as HttpServerResponse from "effect/http/HttpServerResponse";
import { Coder, Models } from "./Coder.ts";
export default class Agent extends Modal.Sandbox<Agent>()(
"Agent",
{ main: import.meta.url, app: Coder },
Effect.gen(function* () {
const models = yield* Modal.ReadVolume(Models);
return {
fetch: Effect.gen(function* () {
const files = yield* models.listFiles("/");
return yield* HttpServerResponse.json(files);
}).pipe(Effect.orDie),
};
}).pipe(Effect.provide(Modal.ReadVolumeHttp)),
) {}
export default class Trainer extends Modal.Sandbox<Trainer>()(
"Trainer",
{
main: import.meta.url,
app: Coder,
image: "nvidia/cuda:12.4.1-runtime-ubuntu22.04",
commands: ["RUN apt-get update && apt-get install -y curl unzip && curl -fsSL https://bun.sh/install | bash && ln -s /root/.bun/bin/bun /usr/local/bin/bun"],
port: 3000,
},
program,
) {}