Superserve
Run Firecracker microVMs on Superserve. Pause and resume, snapshots, egress allow lists, secret bindings, and HTTPS previews.
superserve() runs each sandbox as a Firecracker microVM on Superserve. It calls Superserve’s HTTP API directly, so there is no second package to install.
Install
npm install @opencoredev/sandbox-sdkpnpm add @opencoredev/sandbox-sdkyarn add @opencoredev/sandbox-sdkbun add @opencoredev/sandbox-sdkSet SUPERSERVE_API_KEY on your server, and keep it out of browser bundles. Commands that take stdin use the runtime’s global WebSocket, which Node 22 and Bun have.
Quickstart
import { createSandbox } from "@opencoredev/sandbox-sdk";
import { superserve } from "@opencoredev/sandbox-sdk/superserve";
await using box = await createSandbox(superserve({ installPython: true }));
const result = await box.run("node --version");
console.log(result.stdout);
The default template, superserve/node-22, has no Python, which the adapter needs. installPython: true installs it on first use. await using deletes the VM when the block ends.
What it supports
| Feature | Support | Notes |
|---|---|---|
| Commands and streaming | Partial | Output arrives when the command finishes |
| Files | Yes | mode uses a chmod after the write |
| Background processes | Yes | |
| Preview URLs | Yes | Private by default; send the returned header |
| Pause and resume | Yes | VM state survives |
| Snapshots | Yes | Kept until deleted; no expiresInMs |
| Machine size | No | Set by the template |
| Network policy | Yes | "none" and allow lists of hosts, wildcards, or IPv4 CIDRs |
Hard lifetime (timeoutMs) |
No | Use autoPauseMs and autoDeleteMs |
Idle timeout (idleTimeoutMs) |
No | |
| Keys | Best-effort | Found by metadata lookup |
| Terminal | No |
The capability matrix compares these with every other provider.
Run commands
Await box.run for the result. Iterating works too, but output is read from guest disk after the command exits, so chunks arrive together at the end.
import { createSandbox } from "@opencoredev/sandbox-sdk";
import { superserve } from "@opencoredev/sandbox-sdk/superserve";
await using box = await createSandbox(superserve({ installPython: true }));
const result = await box.run("ls -la", { cwd: "/workspace", timeoutMs: 30_000 });
console.log(result.exitCode, result.stdout);
for await (const chunk of box.run("echo one; echo two")) {
process.stdout.write(chunk.text);
}
Files
Relative paths resolve against /workspace.
import { createSandbox } from "@opencoredev/sandbox-sdk";
import { superserve } from "@opencoredev/sandbox-sdk/superserve";
await using box = await createSandbox(superserve({ installPython: true }));
await box.files.write("notes/todo.txt", "ship the docs\n");
await box.files.write(".env", "TOKEN=abc\n", { mode: 0o600 });
console.log(await box.files.read("notes/todo.txt"));
console.log(await box.files.list("notes"));
Superserve’s file API has no mode, so the SDK runs chmod right after a write that passes mode.
Background processes and previews
No ports to declare. box.preview(port) publishes the port as private and returns the auth header in preview.headers. Send it with every request. With previewAccess: "public", ports are published publicly and preview.headers is absent. Ports must be between 1024 and 65535, except 49983.
import { createSandbox } from "@opencoredev/sandbox-sdk";
import { superserve } from "@opencoredev/sandbox-sdk/superserve";
await using box = await createSandbox(superserve({ installPython: true }));
await box.start("python3 -m http.server 3000", { name: "web", ready: { port: 3000 } });
const preview = await box.preview(3000, { waitMs: 10_000 });
const response = await fetch(preview.url, { headers: preview.headers });
console.log(response.status);
Pause, resume, and snapshots
Pause and resume keep the VM state. snapshot() returns an ID that from accepts.
import { createSandbox } from "@opencoredev/sandbox-sdk";
import { superserve } from "@opencoredev/sandbox-sdk/superserve";
await using box = await createSandbox(superserve({ installPython: true }), { key: "user:123" });
await box.pause();
await box.resume();
const { id } = await box.snapshot();
await using copy = await createSandbox(superserve(), { from: id });
Snapshots outlive their source and have no expiry. Delete one through the API with provider.api.request("DELETE", "/snapshots/{snapshotId}", { params: { snapshotId: id } }). snapshot({ expiresInMs }) with a value above 0 fails with unsupported.
Size, network, and lifetime
Resources come from the template. size, idleTimeoutMs, and timeoutMs fail with unsupported before a VM is allocated.
import { createSandbox, SandboxError } from "@opencoredev/sandbox-sdk";
import { superserve } from "@opencoredev/sandbox-sdk/superserve";
try {
await createSandbox(superserve(), { size: "large" });
} catch (error) {
if (error instanceof SandboxError && error.code === "unsupported") {
console.log("Pick a template with the machine you need.");
}
}
network maps to Superserve egress rules. "none" denies 0.0.0.0/0. An allow list denies everything else and accepts hostnames, *.example.com wildcards (subdomains only, not the apex), IPv4 addresses, and IPv4 CIDRs. When the list holds a hostname, the adapter also allows Superserve’s DNS resolvers, 1.1.1.1/32 and 8.8.8.8/32, because names can’t resolve otherwise. The adapter reads the rules back after creation and fails with unsupported if they differ.
import { createSandbox } from "@opencoredev/sandbox-sdk";
import { superserve } from "@opencoredev/sandbox-sdk/superserve";
await using box = await createSandbox(superserve({ installPython: true }), {
network: { allow: ["registry.npmjs.org", "*.github.com"] },
});
Superserve has no hard lifetime or idle timeout. It has an auto-pause window and an auto-delete window, set on the provider:
import { createSandbox } from "@opencoredev/sandbox-sdk";
import { superserve } from "@opencoredev/sandbox-sdk/superserve";
const provider = superserve({
installPython: true,
autoPauseMs: 30 * 60_000, // pause after 30 minutes of active time per session
autoDeleteMs: 24 * 60 * 60_000, // delete after a day spent paused
});
await using box = await createSandbox(provider);
autoPauseMs counts active time per session and re-arms on resume. autoDeleteMs counts from the pause, and 0 deletes the VM as soon as it pauses. Both round up to whole seconds.
Configuration
| Option | Type | Default | Env var |
|---|---|---|---|
apiKey |
string |
SUPERSERVE_API_KEY |
|
baseUrl |
string |
https://api.superserve.ai |
SUPERSERVE_BASE_URL |
template |
string |
"superserve/node-22" |
|
installPython |
boolean |
false |
|
requestTimeoutMs |
number |
90_000 |
|
autoPauseMs |
number |
Never | |
autoDeleteMs |
number |
Never | |
secretBindings |
Record<string, string> |
None | |
previewAccess |
"private" | "public" |
"private" |
secretBindings maps env var names to Superserve secrets you created: { OPENAI_API_KEY: "openai-prod" }. The sandbox sees a proxy token in the env var, and Superserve attaches the real credential to outbound requests for the secret’s hosts. These are secret names, not values, unlike createSandbox’s secrets option.
A template needs Git, tar, and nohup, plus Python 3 unless installPython is on. Installing Python needs root and package-repository access and slows the first operation. For fast starts, build a template with Python 3 included; the repo’s benchmark has one in bench/templates/superserve/build.ts.
Call the API directly
box.native calls any Superserve endpoint with the adapter’s credentials, retries, and error mapping. {sandboxId} in a path fills in from the sandbox. Superserve has two planes:
"api", the default, is the control plane athttps://api.superserve.ai, sent with your API key."data"is exec and files inside the sandbox. The adapter sends the sandbox’s access token and routing hint, and activates the sandbox again when they expire or the data plane refuses them.
request returns the parsed JSON body. fetch returns the raw Response for bytes and streams. A non-2xx answer rejects with SandboxError from both.
import { createSandbox } from "@opencoredev/sandbox-sdk";
import { superserve } from "@opencoredev/sandbox-sdk/superserve";
await using box = await createSandbox(superserve({ installPython: true }));
const ports = await box.native.request<{ ports: { port: number }[] }>(
"GET",
"/sandboxes/{sandboxId}/preview-ports",
);
console.log(ports.ports);
await box.files.write("report.txt", "done\n");
const file = await box.native.fetch("GET", "/files", {
plane: "data",
query: { path: "/workspace/report.txt" },
});
console.log(await file.text());
provider.api is the same API with no sandbox bound, for account-level calls on the control plane. The data plane needs a sandbox, so it works only on box.native.
import { superserve } from "@opencoredev/sandbox-sdk/superserve";
const provider = superserve();
const sandboxes = await provider.api.request<{ id: string; status: string }[]>("GET", "/sandboxes");
console.log(sandboxes.length);
To use the official SDK, install @superserve/sdk yourself and pass its import to sdk. On a sandbox you get the SDK’s Sandbox from Sandbox.connect, which resumes the sandbox if it is paused. On provider.api you get the Sandbox class.
import { createSandbox } from "@opencoredev/sandbox-sdk";
import { superserve } from "@opencoredev/sandbox-sdk/superserve";
await using box = await createSandbox(superserve({ installPython: true }));
const sandbox = await box.native.sdk(() => import("@superserve/sdk"));
console.log(await sandbox.getInfo());
If @superserve/sdk isn’t installed, sdk rejects with unavailable.
Limitations
- Keys are best-effort. Pass a
locatorfor exact get-or-create. - No sizes, idle timeouts, hard lifetimes, or terminals.
- Egress rules cover IPv4 only.
- Command output isn’t live.
- Connecting to a paused VM resumes it.
How it works
- Commands run under a guest supervisor in their own process group. A timeout or abort triggers a separate cancellation check, and an unconfirmed kill returns
outcome_unknown. - A restore inherits the snapshot’s auto-pause window. The adapter replaces it with
autoPauseMs, or clears it whenautoPauseMsis unset. - Listing uses native metadata and doesn’t wake VMs.
- A failed snapshot reports
outcome_unknownwith the snapshot name. List the source’s snapshots before retrying.