Docs / Serverless Objects / Hotcell binding

Hotcell sandbox binding

Use serverless-objects/hotcell for a Sandbox Durable Object with the Cloudflare Sandbox SDK 0.x call shape, backed by a Hotcell daemon.

serverless-objects/hotcell provides a Sandbox Durable Object and getSandbox() in the shape of the @cloudflare/sandbox 0.x API, backed by a Hotcell daemon instead of Cloudflare Containers. The object stores its Hotcell sandbox id in Durable Object storage, so after the object is evicted or the node restarts, it reattaches to the same sandbox and workspace.

The binding is pre-alpha. @cloudflare/sandbox 1.0, a different Containers-based API, is not implemented here; see compatibility.

Install

npm install serverless-objects

Use

import { getSandbox, proxyToSandbox, Sandbox } from "serverless-objects/hotcell";

export { Sandbox };

export default {
  async fetch(request, env) {
    const sandbox = getSandbox(env.Sandbox, "user-123");
    const result = await sandbox.exec("python3 -c 'print(2 + 2)'");
    return Response.json(result);
  },
};
{
  "durable_objects": { "bindings": [{ "name": "Sandbox", "class_name": "Sandbox" }] },
  "migrations": [{ "tag": "v1", "new_sqlite_classes": ["Sandbox"] }]
}

No containers entry is needed, because Hotcell runs the sandbox. proxyToSandbox() returns null: Hotcell serves preview URLs from its own proxy, and the function is kept so a Cloudflare Worker's fetch handler ports unchanged.

Connect to the daemon

Variable Default Source
HOTCELL_ENDPOINT http://127.0.0.1:4750 Worker vars
HOTCELL_API_KEY none; sent when set Worker secret

Supported methods

Method Notes
exec(command, { cwd, env }) Buffered. Returns success, exitCode, stdout, stderr, command, duration, timestamp.
writeFile, readFile UTF-8 strings only.
mkdir, deleteFile, renameFile, moveFile, exists, listFiles listFiles is not recursive; entries omit mode and permissions.
startProcess, listProcesses, getProcess, killProcess, killAllProcesses, getProcessLogs Processes are plain objects, without kill() or getLogs() methods. Hotcell's combined log is returned as stdout.
setEnvVars
exposePort, unexposePort, getExposedPorts URLs come from Hotcell's preview proxy (port 4751). hostname is accepted and ignored.
destroy Destroys the Hotcell sandbox and forgets its id.
hotcellSandboxId() Not in the Cloudflare API. Returns the Hotcell id, for operators.

Not implemented: execStream, streamProcessLogs, sessions, code contexts (runCode), gitCheckout, bucket mounts, backups, watch, terminals and wsConnect.

Options the binding can't honour, such as exec's timeout or binary encodings, throw a TypeError instead of being silently ignored. Unknown options also throw.

Options

getSandbox(ns, id, options) accepts:

Option Meaning
sleepAfter Seconds, or a string such as "30s", "5m", "1h".
keepAlive When true, sets sleepAfter to 0.
labels Accepted, as in the Cloudflare API.
normalizeId Accepted, as in the Cloudflare API.
transport, containerTimeouts, enableDefaultSession Accepted and ignored (Cloudflare transport settings).
hotcell Hotcell settings: image, env, egress, networked, persist, setup, memoryMb, cpus.

Options apply when the object creates its Hotcell sandbox. An existing sandbox keeps the settings it was created with.

Gateway egress

An egress policy mints a per-sandbox gateway token, so the object never holds a real provider key:

const sandbox = getSandbox(env.Sandbox, "user-123", {
  hotcell: { egress: { spendCapUsd: 5, ttlMs: 3_600_000 } },
});

What has been tested

Unit tests cover the mapping with a fake daemon. One run on macOS arm64 (celld 0.6.2 through sobjects deploy and up, Hotcell 0.1.19 with the container driver) exercised:

  • files, exec, processes and logs;
  • exposePort with a preview fetch;
  • reattachment after celld idle-evicted the object;
  • destroy.

With { hotcell: { egress: { spendCapUsd: 1, ttlMs: 600000 } } }, the sandbox received OPENROUTER_API_KEY and OPENROUTER_BASE_URL pointing at Hotcell's gateway, and the key did not have a provider key's shape. A sandbox created without egress had neither variable.

Spend-cap enforcement and API-key authentication to the daemon have not been exercised.

Source: packages/cli/hotcell.