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/sdkpnpm add @opencoredev/sandbox-sdk @boxd-sh/sdkyarn add @opencoredev/sandbox-sdk @boxd-sh/sdkbun add @opencoredev/sandbox-sdk @boxd-sh/sdkThe 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.comwildcards, 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 withunsupported, because an empty boxd allow list means unrestricted.timeoutMssets boxd’s delete timer. The platform deletes the machine that long after creation, in any state, even suspended.idleTimeoutMssets 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 reportspaused.- 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
locatorfor 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 withsbxsdk-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_unknownwith the snapshot name. - The preview route has boxd’s browser bot challenge turned off so server-side clients can connect.