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

boxd

Persistent, isolated Linux machines through the official boxd SDK. Memory pause, snapshots, fixed-ratio sizes, egress allow lists, lifetimes, persistent disks, and public HTTPS previews.

boxd() runs each sandbox as a persistent, isolated Linux machine on boxd.

Install

npm install @opencoredev/sandbox-sdk @boxd-sh/sdk
pnpm add @opencoredev/sandbox-sdk @boxd-sh/sdk
yarn add @opencoredev/sandbox-sdk @boxd-sh/sdk
bun add @opencoredev/sandbox-sdk @boxd-sh/sdk

The adapter needs @boxd-sh/sdk 0.2.17 or later. Set BOXD_API_KEY on your server, and keep it out of browser bundles.

Quickstart

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

await using box = await createSandbox(boxd());
const result = await box.run("uname -a");
console.log(result.stdout);

await using destroys the machine when the block ends.

What it supports

Feature Support Notes
Commands and streaming Yes Live output
Files Yes mode uses a chmod after the write
Background processes Yes
Preview URLs Partial One public route per machine; a new port moves it
Pause and resume Yes Suspends to memory
Snapshots Yes A restore keeps the source’s size, image, and env; no expiresInMs
Machine size Partial 1, 2, 4, or 8 vCPU at 4 GiB each, 100 GB disk
Network policy Partial Allow lists only; no "none"
Hard lifetime (timeoutMs) Yes Native delete timer
Idle timeout (idleTimeoutMs) Yes Native auto-suspend
Keys Best-effort Read from a marker file in each machine
Terminal No

The capability matrix compares these with every other provider.

Run commands

Await box.run for the result, or iterate it to stream output as it arrives.

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

await using box = await createSandbox(boxd());
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("for i in 1 2 3; do echo $i; sleep 1; done")) {
  process.stdout.write(chunk.text);
}

A timeout or abort kills the command’s whole process group.

Files

Relative paths resolve against /workspace.

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

await using box = await createSandbox(boxd());
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"));

boxd’s file upload has no mode, so the SDK runs chmod right after a write that passes mode.

Background processes and previews

Bind your server to 0.0.0.0. box.preview(port) points the machine’s default https://<machine>.boxd.sh route at that port. A machine has one such route, so previewing a second port changes where earlier URLs point.

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

await using box = await createSandbox(boxd());
await box.start("python3 -m http.server 3000 --bind 0.0.0.0", {
  name: "web",
  ready: { port: 3000 },
});
const preview = await box.preview(3000);
console.log(preview.url);

The URL is public and needs no headers. Add authentication to any app you expose.

Pause, resume, and snapshots

pause() suspends the machine to memory. snapshot() returns a snapshot name that from accepts.

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

await using box = await createSandbox(boxd(), { key: "user:123" });
await box.pause();
await box.resume();

const { id } = await box.snapshot();
await using copy = await createSandbox(boxd(), { from: id });

A restore inherits the snapshot’s size, image, and environment. Passing size or env with from, or a provider with image or volumes, fails with unsupported. Snapshots are kept until deleted, so snapshot({ expiresInMs }) with a value above 0 fails with unsupported.

Size, network, and lifetime

Every size has 4 GiB of memory per vCPU and a 100 GB disk.

Preset vCPU Memory Disk
small 1 4 GiB 100 GB
medium 2 8 GiB 100 GB
large 4 16 GiB 100 GB
import { createSandbox } from "@opencoredev/sandbox-sdk";
import { boxd } from "@opencoredev/sandbox-sdk/boxd";

await using box = await createSandbox(boxd(), {
  size: { cpu: 8, memoryMb: 32_768, diskGb: 100 },
});

An exact size must match that ratio, or it fails with unsupported.

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

await using box = await createSandbox(boxd(), {
  network: { allow: ["registry.npmjs.org", "*.github.com", "140.82.112.0/20"] },
  timeoutMs: 60 * 60_000,
  idleTimeoutMs: 15 * 60_000,
});
  • network: { allow } sets the machine’s egress allow list before create returns, and the adapter checks the list boxd reports back. Entries are hostnames, *.example.com wildcards, or public IPv4 addresses and CIDRs. HTTP and HTTPS are matched by hostname, everything else by address.
  • network: "none" and an empty allow list fail with unsupported, because an empty boxd allow list means unrestricted.
  • timeoutMs sets boxd’s delete timer. The platform deletes the machine that long after creation, in any state, even suspended.
  • idleTimeoutMs sets boxd’s auto-suspend. boxd measures idleness by network traffic, so a background process that makes no traffic doesn’t keep the machine awake. A suspended machine reports paused.
  • Both round up to whole seconds and also apply to a restore with from.

Configuration

Option Type Default Env var
apiKey string BOXD_API_KEY
baseUrl string https://boxd.sh:9443 BOXD_BASE_URL, then BOXD_API_URL
image string boxd’s service image
requestTimeoutMs number 30_000; readiness waits 90_000
fileTransferSignal AbortSignal
volumes { diskId, mountPath, readOnly? }[] None

volumes attaches boxd disks you created with the native client.disks.create(name, "10G"). A disk outlives the machine, so it is the way to keep data across sandboxes. A disk can be attached to one machine at a time.

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

await using box = await createSandbox(
  boxd({ volumes: [{ diskId: "disk_123", mountPath: "/data" }] }),
);
await box.run("echo kept > /data/state");

baseURL is a deprecated alias for baseUrl. An image needs Python 3, Git, GNU tar, and nohup; a non-root image also needs passwordless sudo -n. box.native is { machine, client }, the machine metadata and the official Boxd client.

Limitations

  • Keys are best-effort. Pass a locator for exact get-or-create.
  • No network: "none" and no terminals.
  • One preview route per machine.
  • Listing reads guest metadata, which can wake sleeping machines.

How it works

  • Keys, labels, and creation time live in /var/lib/sandbox-sdk/metadata.json. Machine names starting with sbxsdk- mark candidates. Keep both intact, and keep secrets out of labels.
  • Machines are created isolated, with auto-suspend off unless you set idleTimeoutMs, and auto-destroy off. Isolation removes metadata access and machine peers but does not block the internet. boxd’s own idle auto-hibernate (4 hours by default) still applies.
  • Large reads use bounded exec calls, and each chunk retries once on a timeout. Writes and commands are never replayed.
  • A failed snapshot capture is deleted so retries don’t leave billable artifacts. If that cleanup can’t be confirmed, you get outcome_unknown with the snapshot name.
  • The preview route has boxd’s browser bot challenge turned off so server-side clients can connect.

Next steps

Was this page helpful?