Skip to content
Sandbox SDK
Esc
↑↓navigate↵open⌘Jpreview
On this page

Sandboxes and keys

Create a sandbox, give it a key so you get the same one back next time, and reattach to it from any server process.

A sandbox is one isolated Linux machine at a provider. You get a handle to it from createSandbox, and every other call goes through that handle. Most apps want one sandbox per user or per project that survives between requests. A key gives you that.

Create a sandbox

import { createSandbox } from "@opencoredev/sandbox-sdk";
import { e2b } from "@opencoredev/sandbox-sdk/e2b";

await using box = await createSandbox(e2b(), {
  env: { NODE_ENV: "development" },
  labels: { app: "builder" },
});

console.log(box.id, box.provider);

box.id is the provider’s native ID. box.provider is the provider name, here "e2b". The full option list is on the reference page.

Get or create with a key

Pass key and createSandbox looks for a sandbox with that key first. It creates one only if none exists.

import { createSandbox } from "@opencoredev/sandbox-sdk";
import { e2b } from "@opencoredev/sandbox-sdk/e2b";

const userId = "user_123";

await using box = await createSandbox(e2b(), { key: `user:${userId}` });

if (box.created) {
  await box.git.clone("https://github.com/vercel/next.js", { dir: "app", depth: 1 });
}

box.created is true when this call made the sandbox and false when it found an existing one. Use it to run setup only once.

With a key, await using calls box.close() at the end of the block, not destroy(). The sandbox keeps running so the next request finds it. See Lifecycle and cleanup.

Exact and best-effort keys

How reliable the lookup is depends on the provider.

Provider Key mode How the key is stored
Local exact Container name, a hash of the key
Vercel exact Sandbox name, a hash of the key
Memory exact In-process map
Local (agentOS) exact, in-process In-process map
E2B best-effort Sandbox metadata
Daytona best-effort Sandbox labels
Every other cloud provider best-effort Metadata, the VM name, or a marker file in the VM. Each provider page says which

Exact means the provider enforces unique names, so two concurrent calls with the same key always land on one sandbox. Best-effort means the SDK finds the sandbox by searching metadata. Two requests that arrive at the same moment can both miss and create two sandboxes.

If that race matters, pass a locator. It is two functions backed by any key-value store you already run.

import { createSandbox } from "@opencoredev/sandbox-sdk";
import { e2b } from "@opencoredev/sandbox-sdk/e2b";
import { kv } from "./kv";

await using box = await createSandbox(e2b(), {
  key: "project:42",
  locator: {
    get: (key) => kv.get(key),
    set: (key, id) => kv.set(key, id),
    delete: (key) => kv.delete(key),
  },
});

The SDK reads the ID from get, and writes a new one with set after it creates a sandbox. If the stored ID points at a sandbox that no longer exists, the SDK creates a new one and stores that ID instead. Within one process, the SDK also queues calls for the same key, so they never race each other.

Across servers, get followed by set is not atomic, so these two functions alone can’t stop two servers from creating a sandbox each. For a guarantee, add withLock and ask for keyMode: "exact":

import { createSandbox } from "@opencoredev/sandbox-sdk";
import { e2b } from "@opencoredev/sandbox-sdk/e2b";
import { kv } from "./kv";

await using box = await createSandbox(e2b(), {
  key: "project:42",
  keyMode: "exact",
  locator: {
    get: (key) => kv.get(key),
    set: (key, id) => kv.set(key, id),
    withLock: (key, action) => kv.withLock(`sandbox:${key}`, action),
  },
});

withLock must run action while holding a distributed lock or transaction for that key. keyMode: "exact" on a best-effort provider without withLock fails with unsupported.

Reattach by ID

connectSandbox attaches to a sandbox you already know the ID of. It never creates one.

import { connectSandbox } from "@opencoredev/sandbox-sdk";
import { e2b } from "@opencoredev/sandbox-sdk/e2b";

const box = await connectSandbox(e2b(), "sbx_abc123");
const result = await box.run("ls");
console.log(result.stdout);
await box.close();

It throws SandboxError with code not_found if the sandbox is gone.

What a handle exposes

Member What it is
box.id Native sandbox ID
box.key The key you passed, if any
box.provider Provider name, such as "vercel"
box.created Whether this call created the sandbox
box.labels Labels on the sandbox, including sandbox-sdk
box.capabilities Optional features this provider supports
box.native The provider’s own API, typed per provider

Use box.native when you need a provider feature the SDK doesn’t wrap. For providers that call their API directly, it is a NativeApi that calls any endpoint and can load the official SDK on demand. For providers that wrap an SDK, it is the same object that SDK returns.

Optional capabilities

Some methods exist only on providers that support them: pause(), resume(), snapshot(), and terminal(). They are on the TypeScript type only when the provider has them, so calling box.pause() on a provider without pause is a type error, not a runtime surprise. box.capabilities holds the same set at runtime.

import { createSandbox } from "@opencoredev/sandbox-sdk";
import { e2b } from "@opencoredev/sandbox-sdk/e2b";

await using box = await createSandbox(e2b(), { key: "project:42" });

await box.run("npm install");
await box.pause();

The capability matrix lists which provider supports what.

Was this page helpful?