How-to: manage sandbox lifecycle
Recipes for pausing, resuming, forking, recharging bandwidth, and destroying sandboxes. For the underlying concepts — state machine, billing model, fork semantics — see Lifecycle.
At a glance
- Package:
@nodeops-createos/sandbox(npm) - Import:
import { createClient } from "@nodeops-createos/sandbox" - Base URL:
https://api.sb.createos.sh— override withCREATEOS_SANDBOX_BASE_URL - Auth: API key via the
apiKeyoption orCREATEOS_SANDBOX_API_KEY
Pause to stop billing, resume later
Problem
You want to preserve a sandbox's disk and memory state between tasks without paying for idle compute time.
Solution
Call pause() and confirm the transition with waitUntilPaused(). The
sandbox snapshot is stored; billing for compute stops. When you need it back,
call resume() and wait with waitUntilRunning().
TypeScript1import { CreateosSandboxClient } from "@nodeops-createos/sandbox";23const client = new CreateosSandboxClient();4const sandbox = await client.createSandbox({ shape: "s-4vcpu-4gb", rootfs: "devbox:1" });56try {7 // ... do work ...89 // Snapshot and suspend. The handle transitions to pausing → paused.10 await sandbox.pause();11 await sandbox.waitUntilPaused();12 console.log("paused:", sandbox.status); // "paused"1314 // Later — restore. The handle transitions to resuming → running.15 await sandbox.resume();16 await sandbox.waitUntilRunning();17 console.log("running:", sandbox.status); // "running"1819 // ... continue work ...20} finally {21 await sandbox.destroy();22}
pause() throws CreateosSandboxValidationError if the sandbox is not in a
pausable state (e.g. already pausing or destroyed). Both pollers accept a
timeoutMs option:
TypeScript1await sandbox.waitUntilPaused({ timeoutMs: 30_000 });
Auto-pause an idle sandbox
Problem
You want the control plane to pause the sandbox automatically when it sits idle, without the client polling for idleness itself.
Solution
Set auto_pause_after_seconds at create time, or update it on a live sandbox
with setAutoPause(seconds). Pass null to disable.
TypeScript1import { CreateosSandboxClient } from "@nodeops-createos/sandbox";23const client = new CreateosSandboxClient();45// Option A — bake the timeout in at create. Valid range: 60–86400 (1 min – 24 h).6const sandbox = await client.createSandbox({7 shape: "s-4vcpu-4gb",8 rootfs: "devbox:1",9 auto_pause_after_seconds: 300, // pause after 5 min idle10});1112try {13 // Option B — change the timeout on a running sandbox.14 await sandbox.setAutoPause(600); // update to 10 min15 console.log("timeout:", sandbox.data.auto_pause_after_seconds); // 6001617 // Disable auto-pause entirely.18 await sandbox.setAutoPause(null);19 console.log("timeout:", sandbox.data.auto_pause_after_seconds ?? "off"); // "off"20} finally {21 await sandbox.destroy();22}
setAutoPause refreshes the handle in place, so sandbox.data.auto_pause_after_seconds
reflects the new value immediately after the call returns.
The server rejects values outside 60–86400 with CreateosSandboxValidationError.
When the idle timeout fires, the control plane pauses the sandbox exactly as if
you had called pause() — compute billing stops, disk and memory state are
preserved.
Fork (branch) a sandbox
Problem
You want to create one or more independent copies of a sandbox from a known checkpoint — for example to run parallel experiments from the same base state.
Solution
Pause the sandbox (fork requires paused state), then call fork(). Each call
returns a handle to a new, fully independent sandbox. The parent stays paused;
you can fork from it again or resume it independently.
TypeScript1import { CreateosSandboxClient } from "@nodeops-createos/sandbox";23const client = new CreateosSandboxClient();4const parent = await client.createSandbox({ shape: "s-4vcpu-4gb", rootfs: "devbox:1" });5let branchA: Awaited<ReturnType<typeof parent.fork>> | undefined;6let branchB: Awaited<ReturnType<typeof parent.fork>> | undefined;78try {9 // ... install deps, set up state ...1011 // Pause parent before forking.12 await parent.pause();13 // Fork can occasionally stick in a pausing state on the control plane —14 // always wait for paused before relying on the fork.15 await parent.waitUntilPaused();1617 // Fork two independent branches from the same checkpoint.18 branchA = await parent.fork(); // auto-resumes19 branchB = await parent.fork(); // auto-resumes2021 await branchA.waitUntilRunning();22 await branchB.waitUntilRunning();2324 // The branches are independent — changes in one do not affect the other.25 console.log("branch A:", branchA.id);26 console.log("branch B:", branchB.id);27 console.log("parent still paused:", parent.status); // "paused"2829 // ... run experiments on branchA and branchB concurrently ...30} finally {31 await Promise.allSettled([32 branchA?.destroy(),33 branchB?.destroy(),34 parent.destroy(),35 ]);36}
To keep a fork paused instead of auto-resuming, pass start_paused: true:
TypeScript1const clone = await parent.fork({ start_paused: true });2// clone.status === "paused"
fork() throws CreateosSandboxValidationError if the source sandbox is not in
a forkable state. The source must be paused before forking.
Grow bandwidth quota after create
Problem
You want to raise a running sandbox's egress cap — either proactively or because
BandwidthView.capped is true.
Solution
Read the current quota with getBandwidth(), then add bytes with
rechargeBandwidth(addBytes).
TypeScript1import { CreateosSandboxClient } from "@nodeops-createos/sandbox";23const GiB = 1024 ** 3;45const client = new CreateosSandboxClient();6const sandbox = await client.createSandbox({ shape: "s-4vcpu-4gb", rootfs: "devbox:1" });78try {9 const bw = await sandbox.getBandwidth();10 console.log(`quota: ${bw.quota_bytes} used: ${bw.used_bytes} capped: ${bw.capped}`);1112 if (bw.capped) {13 const updated = await sandbox.rechargeBandwidth(10 * GiB); // +10 GiB14 console.log(`new quota: ${updated.quota_bytes}`);15 }16} finally {17 await sandbox.destroy();18}
bandwidth_quota_bytes is not settable at create time. The server rejects
non-zero values at create with a 400. Use rechargeBandwidth() post-create as
the only supported path to grow the cap. quota_bytes === -1 means unmetered;
rechargeBandwidth is a no-op on unmetered sandboxes.
Destroy and confirm
Problem
You want to tear down a sandbox and be certain the resource has been fully reclaimed before proceeding.
Solution
destroy() is async server-side: the call returns when the row reaches
destroying, but reclamation may still be in progress. Use waitUntilDestroyed()
to block until the row is fully reclaimed.
TypeScript1import { CreateosSandboxClient } from "@nodeops-createos/sandbox";23const client = new CreateosSandboxClient();4const sandbox = await client.createSandbox({ shape: "s-4vcpu-4gb", rootfs: "devbox:1" });56try {7 // ... do work ...8} finally {9 const result = await sandbox.destroy();10 // result.status is "destroying" | "destroyed"1112 // Block until fully reclaimed if needed (e.g. in tests, or before reusing13 // the same name/slot).14 await sandbox.waitUntilDestroyed();15 console.log("reclaimed:", sandbox.status); // "destroyed"16}
waitUntilDestroyed() treats destroying as an intermediate step and does not
abort on it — only error and failed states cause it to throw.
Pause vs. fork
Pause suspends the same sandbox. Its id is unchanged. Resume picks up exactly where it left off. Use it to stop billing between sessions.
Fork creates a new, independent sandbox from the paused snapshot. The parent keeps its id and stays paused. Use it to branch experiments or spin up parallel workloads from a shared base.
For deeper treatment of the state machine and billing model, see Lifecycle.
See also
- Reference: Sandbox — full method signatures and
parameter tables for
pause,resume,fork,destroy,setAutoPause,getBandwidth,rechargeBandwidth, and thewaitUntil*pollers.