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; exposePortwith 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.