Sandboxes
Apply reusable Sandbox templates and launch ephemeral Sandboxes with ccp.
ccp apply also manages Sandbox templates — versioned, organization-scoped
recipes for ephemeral VMs. Applying a template reconciles its stable identity
and admits one immutable build for its exact recipe; ccp sandbox create
launches a fresh, short-lived Sandbox from a template's current ready build.
Apply a template
ccp apply -f sandbox.yaml --org-id "$CCP_ORG_ID"Reapplying an unchanged manifest reuses the same template and its ready or
in-progress build — it never creates a duplicate. Pass --dry-run to run the
same reconciliation without writing a template or build.
A SandboxTemplate manifest looks like:
apiVersion: sandboxes.clusterbase.ai/v1
kind: SandboxTemplate
metadata:
name: python-tools
spec:
base: debian
resources:
vcpu: 2
memory_mb: 1024
packages:
apt: [jq, python3, python3-venv]
ports:
- name: cdp
port: 9222
protocol: websocket
steps:
- run: |
python3 -m venv /opt/python
/opt/python/bin/pip install cowsay==6.1
mkdir -p /workspace
touch /workspace/readymetadata.name is the stable organization-scoped template identity.
spec.resources is required and must be one supported vcpu / memory_mb
pair — 1/256, 1/512, 2/1024, 4/2048, or 4/4096 (vCPU/MiB); any other
pair is rejected. spec.base is exactly one of debian or alpine; Infra
resolves it to a committed immutable minimal system build. Debian supports
apt and Alpine supports apk — incompatible package managers are rejected
before build admission. Language runtimes aren't preinstalled on either base:
add the runtime as an OS package and install language dependencies in the
run step. The retired ubuntu base and the full development image are
not accepted as manifest bases. Exactly one run step follows the package
lists.
spec.ports declares private, immutable services the running Sandbox exposes.
Each entry has a unique DNS-label name, a unique guest port, and a
protocol of http or websocket; at most eight may be declared. Your
template's boot setup must start the service and bind it to the guest
interface — declaring a port doesn't start anything. A running Sandbox
publishes each declared service behind an authenticated HTTPS or WSS route;
raw VM addresses and undeclared ports stay private. Use ccp sandbox connect
(below) to mint access to a declared service.
Changing the base, resources, packages, ports, or the run step admits a new immutable build; existing builds are never mutated. Build execution, publication, scheduling, and every Sandbox launch use the exact resource pair snapshotted by that build.
Keep manifests commit-safe. Unknown fields are rejected, including plaintext
secrets or env blocks and mutable templateId, templateBuildId, vmId,
or sandboxId fields — build and runtime identities are server-derived.
Launch a Sandbox
Ephemeral creation is a separate lifecycle operation from applying a template. Creating a Sandbox launches the applied template's current exact ready build:
ccp sandbox create --template python-tools --ttl 15m --org-id "$CCP_ORG_ID"--ttl defaults to 15m and accepts whole-second human durations from 1m
through 24h. Every invocation creates a distinct Sandbox while reusing the
same immutable build; applying a template's desired state never launches a
Sandbox on its own.
create fails with template_not_found when the active organization-scoped
name doesn't exist, and template_not_ready while its current recipe has no
ready build yet — wait for the applied build to publish, then retry.
Persistent Sandboxes
ccp sandbox create --ttl always sends a bounded lifetime between 1m and
24h — there's no CLI flag to skip expiry. Callers hitting the Sandbox
creation API directly can pass ttl_seconds: 0 instead of a bounded value to
launch a Sandbox with no age-based expiry; every subsequent response reports
expires_at: null for it.
A Sandbox that already started with a bounded lifetime can be adopted as persistent later:
curl -X POST https://api.clusterbase.dev/api/v1/sandboxes/sbx_.../retain \
-H "Authorization: Bearer $(ccp auth print-access-token)"Retaining clears the Sandbox's age deadline immediately and is idempotent —
repeating it is a no-op. It fails rather than reviving the Sandbox once the
Sandbox is deleting or already deleted. Persistent Sandboxes keep the same
idle pause/resume behavior as any other desktop Sandbox (below), and
short-lived grants like ccp sandbox connect access and desktop-control
leases keep their own bounded expiry regardless of the Sandbox's own
lifetime.
Connect to a declared service
Mint short-lived, scoped access to one of the template's declared ports
services:
ccp sandbox connect sbx_... cdp --ttl 5m
ccp sandbox connect sbx_... cdp --ttl 5m --json--ttl defaults to 15m and accepts whole-second human durations from 1s
through 1h, never beyond the Sandbox's own remaining TTL. The response keeps
the endpoint and opaque bearer token separate — attach
Authorization: Bearer <token> to the HTTPS request or the WebSocket Upgrade;
the token is never embedded in the URL. --json emits the stable automation
contract with endpoint, token, and expires_at fields.
Connecting validates the organization, the Sandbox's remaining lifetime, and
the exact service declaration atomically when minting access — a service name
not declared in the template's spec.ports, or a Sandbox past its TTL, is
rejected rather than granted a stale or partial token.
Public preview on the development template
A Sandbox created from the built-in development system template via the
console or the Infra API always carries one fixed, system-owned HTTP service
named preview on port 8000. Unlike ports you
declare yourself, preview is published without authentication — anyone with
the URL can reach it, so treat it as a shareable preview link, not a private
endpoint. Any port your own template declares stays private and
authenticated; a template recipe cannot mark its own ports public. The
development image now runs on Debian rather than Ubuntu; custom
SandboxTemplate manifests can no longer select this full image as a
spec.base — see Apply a template.
While the Sandbox is running, ccp sandbox create and every subsequent get
or list call return the published preview as an opaque URL under
services in the response, alongside the service's name and protocol. The
entry disappears once the Sandbox stops or expires.
Until the dev server behind preview starts accepting connections, the URL
serves a branded HTML waiting page (still with a 502 status) that retries
every two seconds instead of a plain-text Bad Gateway. It automatically
transitions to the app once the server responds. A failed WebSocket upgrade
still returns the plain-text 502.
Watch Sandboxes in the console
The console lists every Sandbox in the active organization under
Build → Sandboxes at console.clusterbase.ai,
with its exact template build, lifecycle status, creation time, and remaining
TTL. Open a Sandbox to see the same metadata, or delete it from the row menu
or the detail page. The console only reports what Infra reports: it never
owns expiry, and creation stays with ccp sandbox create.
Open a graphical desktop
The graphical Ubuntu desktop is a native system template with a desktop
environment and a noVNC service. It uses the stable desktop system
identity, so it needs no organization-owned manifest, ccp apply, or custom
build — create a Sandbox from it directly:
ccp sandbox create --template desktop --ttl 1h --org-id "$CCP_ORG_ID"Open its read-only desktop without ever putting a bearer token in a browser URL:
ccp sandbox desktop sbx_... --ttl 20m--ttl defaults to 15m and accepts whole-second durations from 1s
through 1h. The command opens a token-free 127.0.0.1 URL and stays in
the foreground: it injects the scoped desktop service bearer into the
upstream HTTPS and WebSocket requests, rejects cross-origin browser access,
and closes on Ctrl-C or access expiry. The guest VNC server enforces
view-only access on this transport — browser settings cannot enable pointer,
keyboard, or clipboard input — and a separate, reserved control transport is
not published by ordinary route access. Raw VNC is never published — it
stays loopback-only inside the guest. For non-browser automation that can
set its own Authorization header, use ccp sandbox connect instead.
First-party browser and native desktop clients may also connect directly to
the desktop view (or control) Upgrade without a local ccp sandbox desktop
tunnel, since browser WebSocket APIs can't set Authorization themselves.
Pass the capability as an exact, ordered WebSocket subprotocol offer instead:
binary, bearer.sbxt_<base64url>The server selects only binary and never echoes the bearer back. Browser
requests must come from an HTTPS Origin the target environment allows;
native clients may omit Origin. Keep the bearer in memory only — never in a
URL or persistent browser storage — and request a fresh capability once it
expires. If a client can set request headers, Authorization: Bearer <token> still works and takes priority over the subprotocol form when both
are present.
A desktop Sandbox auto-pauses after 15 minutes with no connected viewer,
control lease, or model ComputerAct/ComputerObserve activity, freeing its
scheduled memory while preserving disk and in-memory VM state. The next
ccp sandbox desktop connection, ccp sandbox connect, or ComputerObserve
call resumes the same VM transparently before serving the request. This idle
pause is independent of the Sandbox's own lifetime: a bounded Sandbox keeps
tearing down on schedule whether or not it's currently paused, while a
persistent Sandbox has no such deadline to enforce.
A fresh desktop Sandbox uses a prewarmed Blink microVM browser: the snapshot already has Blink running on a blank, minimized window, so the Browser launcher restores that existing window (or opens a new one in the resident process) instead of a cold start. If the process has exited, the launcher restarts the supervised browser service; new windows open maximized. A normal close leaves the supervised process resident. The desktop's Blink is machine-managed to force-install its bundled uBlock Origin, with managed filters covering advertising, privacy, cookie, and newsletter rulesets enabled by default. Ads, cookie-consent banners, and intrusive overlays are suppressed automatically.
Take input control of a desktop
The reserved desktop-control transport is the one that carries input. Its
guest services are stopped by default, and neither ccp sandbox desktop nor
ccp sandbox connect opens it. An organization member or an authenticated
trusted service acquires it directly against the infra API:
curl -X POST https://api.clusterbase.dev/api/v1/sandboxes/sbx_.../desktop-control \
-H "Authorization: Bearer $(ccp auth print-access-token)" \
-H "Content-Type: application/json" \
-d '{"lease_id":"sbxcl_...","token":"sbxt_...","ttl_seconds":300}'
# {"lease_id":"sbxcl_...","fence":1,"endpoint":"wss://...","token":"sbxt_...","expires_at":"..."}The caller generates a fresh sbxcl_<uuid> lease ID and a fresh 256-bit
sbxt_<base64url> bearer; ttl_seconds accepts 10 through 900, and the
lease is additionally capped at the Sandbox's own expiry. A successful
response carries Cache-Control: no-store and returns the lease ID, its
monotonically increasing fence, the WSS endpoint, the bearer, and
expires_at. Infra persists only the bearer digest — retain the returned
token, since it can't be recovered later. Retrying an identical acquisition
while a matching lease is still active recovers the same capability instead
of failing.
PATCH the same path with lease_id, fence, and a new ttl_seconds to
renew the lease; DELETE it with lease_id and fence to release it. A
stale lease ID or fence can't renew or release the lease that replaced it.
While a control lease is active, model-driven ComputerAct input is
rejected for that Sandbox. Release, expiry, or Sandbox deletion revokes the
lease, closes any open control WebSockets, stops the guest control
services, clears the desktop's X11 clipboard and primary selection (plus
legacy cut buffers), and invalidates the model's current frame — automation
must take a fresh observation before it can act again. If clearing the
clipboard fails, model input stays rejected so a hand-back can be retried
safely rather than risking human clipboard contents leaking into resumed
model control. ccp sandbox desktop stays view-only throughout and never
grants or reflects this lease.
Browse workspace files over HTTP
A running Sandbox exposes its guest workspace read-only over the normal Infra
bearer token — no declared port or ccp sandbox connect required:
curl https://api.clusterbase.dev/api/v1/sandboxes/sbx_.../files?path=/home/user \
-H "Authorization: Bearer $(ccp auth print-access-token)"
curl "https://api.clusterbase.dev/api/v1/sandboxes/sbx_.../files/content?path=/home/user/notes.txt" \
-H "Authorization: Bearer $(ccp auth print-access-token)"GET /files lists one directory, defaulting to the workspace root
(/home/user for managed Build); pass ?path= to expand any other
directory. The response contains path, entries (each with name,
path, kind, and size), and truncated. Hidden and ignored files are
included, and directories sort first.
GET /files/content reads bounded UTF-8 text, returning path, content,
size, and truncated; the preview is limited to 1 MiB. URL-encode paths,
including spaces. Binary files return 415, and files over the guest input
limit return 413.
Both routes reject paths or symlinks that resolve outside the workspace,
return 403 if the caller has lost organization access, 404 for a
missing path, and 409 if the Sandbox isn't running. Neither route writes,
renames, or deletes files.