Skip to content

feat(e2b): add the E2B provider - #2930

Open
sam-goodwin wants to merge 11 commits into
mainfrom
feat/e2b-provider
Open

sam-goodwin wants to merge 11 commits into
mainfrom
feat/e2b-provider

Conversation

@sam-goodwin

@sam-goodwin sam-goodwin commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

@alchemy.run/e2b: E2B templates, warm snapshots, volumes, secrets and webhooks as resources, and sandboxes created from any Worker or Function. Depends on alchemy-run/distilled#1032 and #2928 (merged in).

  • 7 resources (Template, Sandbox, Snapshot, Volume, Secret, Webhook, ApiKey), 13 bindings, 1 event source; every resource takes lookup
  • 12 live test files, 1 unit test file, 3 examples (e2b-agent-sandbox, e2b-program, e2b-program-async), a 16-page docs hub

Templates

E2B builds remotely, so deploys need no Docker. The source is planned with @alchemy.run/docker/BuildPlan under E2B's Capabilities and lowered to E2B's build steps. COPY sources are uploaded once per content hash, and a Dockerfile E2B can't build fails at plan.

export const Agent = E2B.Template("Agent", {
  dockerfile: Dockerfile.inline`
    FROM node:22-bookworm
    RUN apt-get update && apt-get install -y git ripgrep
  `,
  cpuCount: 2,
  memoryMB: 2048,
});

Sandboxes from a Worker

export default class Api extends Cloudflare.Worker<Api>()(
  "Api",
  { main: import.meta.url },
  Effect.gen(function* () {
    const sandboxes = yield* E2B.CreateSandbox(Agent);
    return {
      fetch: Effect.gen(function* () {
        const request = yield* HttpServerRequest;
        const sandbox = yield* Effect.acquireRelease(
          sandboxes.create({ timeout: 120, network: { allowOut: [] } }),
          (sandbox) => Effect.ignore(sandbox.kill()),
        );
        const code = yield* request.text;
        yield* sandbox.files.write("main.mjs", code);
        const result = yield* sandbox.exec("node main.mjs");
        return yield* HttpServerResponse.json(result);
      }).pipe(Effect.scoped, Effect.orDie),
    };
  }).pipe(Effect.provide(E2B.CreateSandboxHttp)),
) {}

create takes E2B's own parameters. The handle has exec (a shell line or an argv; non-zero exits are returned), start (a scoped Process with output, write, kill, wait), files, pty.open, fetch to a port, and E2B's own pause, resume, fork, snapshot and network updates. A call into a paused sandbox fails with SandboxNotRunning. Sandboxes a host creates carry its stage in their metadata and are killed when the host is destroyed.

A program in every sandbox

export default class Program extends E2B.Template<Program>()(
  "Program",
  { main: import.meta.url, port: 3000 },
  Effect.gen(function* () {
    return { fetch: Effect.succeed(HttpServerResponse.text("hello from a sandbox")) };
  }),
) {}

E2B snapshots the template once the program answers, so every sandbox boots with it serving. A plain Node main works too (E2B.InferEnv).

A sandbox the stack owns

export const DevBox = E2B.Sandbox("DevBox", {
  template: Agent, // a rebuilt Agent replaces the sandbox
  timeout: "10 minutes",
  mounts: [{ volume: Cache, path: "/cache" }],
});

// in a Worker
const devBox = yield* E2B.ConnectSandbox(DevBox);
const test = yield* devBox.exec("git pull && npm test", { cwd: "/home/user/repo" });

The sandbox pauses when idle with its memory and files, and the next request resumes it. ConnectSandbox returns the same handle as create. Give it a template program and it serves that program at a stable URL.

Warm snapshots

const warm = yield* E2B.Snapshot("Warm", {
  template: agent.templateId,
  run: ["git clone https://github.com/my-org/app", "cd app && npm ci"],
});

Volumes

export const Reports = E2B.Volume("Reports", {});

const reports = yield* E2B.ReadVolume(Reports);
const data = yield* E2B.MountVolume(Reports, { path: "/reports" });
const volumes = yield* E2B.CreateVolume();
// in fetch:
const home = yield* volumes.getOrCreate(`home-${user}`);
const sandbox = yield* sandboxes.create({ mounts: [data, { volume: home, path: "/home/user" }] });

In a template program, MountVolume puts the mount on the template, so every sandbox from it mounts the volume.

Secrets at egress

const secrets = yield* E2B.CreateSecret();
const github = yield* secrets.put(`github-${user}`, Redacted.make(token));
const sandbox = yield* sandboxes.create({
  network: {
    allowOut: ["api.github.com"],
    rules: { "api.github.com": [{ transform: { headers: { Authorization: `Bearer ${github.ref}` } } }] },
  },
});

Lifecycle events

yield* E2B.consumeSandboxEvents({ events: ["sandbox.lifecycle.killed"] }, (event) =>
  Effect.log(`${event.sandbox_id} killed`),
);

Also E2B.Template.lookup({ name }) (and the same on every resource), E2B.Webhook, E2B.ApiKey, CreateTemplate, CreateSnapshot, WriteSecret, ReadTeam and ReadWebhook; see the generated reference.

@alchemy-version-bot

alchemy-version-bot Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Install the packages built from this commit:

alchemy

pnpm install https://pkg.alchemy.run/alchemy/pr:2930:81847c4
@alchemy.run (29)
pnpm install https://pkg.alchemy.run/@alchemy.run/better-auth/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@alchemy.run/git/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@alchemy.run/drizzle/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@alchemy.run/acme/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@alchemy.run/axiom/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@alchemy.run/doppler/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@alchemy.run/infisical/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@alchemy.run/turso/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@alchemy.run/e2b/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@alchemy.run/planetscale/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@alchemy.run/neon/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@alchemy.run/prisma/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@alchemy.run/hetzner/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@alchemy.run/railway/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@alchemy.run/fly/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@alchemy.run/github/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@alchemy.run/stripe/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@alchemy.run/cloudflare/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@alchemy.run/gcp/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@alchemy.run/aws/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@alchemy.run/domain/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@alchemy.run/kubernetes/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@alchemy.run/docker/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@alchemy.run/redis/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@alchemy.run/sql/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@alchemy.run/cloudflare-runtime/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@alchemy.run/frontend-frameworks/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@alchemy.run/node-utils/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@alchemy.run/pkg/pr:2930:81847c4
Transitive Dependencies (21)
pnpm install https://pkg.alchemy.run/@distilled.cloud/aws/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@distilled.cloud/acme/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@distilled.cloud/axiom/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@distilled.cloud/core/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@distilled.cloud/doppler/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@distilled.cloud/infisical/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@distilled.cloud/turso/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@distilled.cloud/e2b/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@distilled.cloud/planetscale/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@distilled.cloud/neon/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@distilled.cloud/s3/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@distilled.cloud/prisma/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@distilled.cloud/hetzner/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@distilled.cloud/railway/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@distilled.cloud/fly-io/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@distilled.cloud/github/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@distilled.cloud/stripe/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@distilled.cloud/aws4signer/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@distilled.cloud/cloudflare/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@distilled.cloud/iceberg/pr:2930:81847c4
pnpm install https://pkg.alchemy.run/@distilled.cloud/gcp/pr:2930:81847c4

Published Nov 6, 2026, 7:19 PM UTC. Expires Nov 13, 2026, 7:19 PM UTC, extended while this pull request is open.

… install, baked args, secrets, push

- alchemy/Mount (from the Archil branch) with ECS Task, Cloudflare Container and ImageSource wiring
- alchemy/Sandbox: the handle vocabulary sandbox providers extend
- alchemy/RuntimeObjects: the ownership stamp and destroy-time sweep for runtime-created objects
- BuildPlan: install lines, buildArgs/secrets capabilities, FROM --platform checks, checkContent, push, registry renamed to pull
@sam-goodwin sam-goodwin added the deploy-website Deploy the website preview for this PR label Nov 5, 2026
@alchemy-version-bot

alchemy-version-bot Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Website Preview Deployed

URL: https://pr-2930-alchemy-website-preview.testing-2b2.workers.dev

Built from commit 81847c4.


This comment updates automatically with each push.

@sam-goodwin
sam-goodwin marked this pull request as ready for review November 6, 2026 05:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

deploy-website Deploy the website preview for this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant