Platform

Sandboxes

Apply reusable Sandbox templates and launch ephemeral Sandboxes with ccp.

View as Markdown

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/ready

metadata.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.

On this page