Self-host sandbox / computer providers

SANDBOX_PROVIDER selects where bot computers run. Workspace bots share a Team Computer by default; Private Computers are optional. See computer runtime and isolation for the sharing and persistence contract.

Quick pick

Goal Set Also need
Local Docker computers SANDBOX_PROVIDER=docker SANDBOX_SUPERVISOR_TOKEN, computer image, Docker socket for supervisor
UI only, no computers SANDBOX_PROVIDER=none No provider credential; published-images Compose still requires SANDBOX_SUPERVISOR_TOKEN
Managed remote desktop e2b / novita / daytona / boat / boxd SANDBOX_SUPERVISOR_TOKEN (published-images Compose), matching API key (and optional URL knobs for Daytona/Boat)
One pod per computer on your own cluster SANDBOX_PROVIDER=kubernetes API and worker running in the cluster, a namespace and a Role that can create, read, delete and exec into pods

Published-images Compose defaults to docker. It always starts the supervisor and requires SANDBOX_SUPERVISOR_TOKEN, including for none and remote providers. This stack credential does not replace a remote provider’s API key.

docker (in-stack supervisor)

Compose starts a sandbox supervisor (from the app image) on the internal network (port 7091 in published-images). It creates sibling computer containers from RAKAZO_COMPUTER_IMAGE + RAKAZO_COMPUTER_IMAGE_TAG.

Requirements:

  • Non-empty SANDBOX_SUPERVISOR_TOKEN (distinct from auth / screen / encryption secrets)
  • Reachable computer image (GHCR default or your mirror; on arm64 pin multi-arch tags for both app and computer)
  • Docker Engine available to the supervisor (socket mount on the Compose path)

Verify:

curl -fsS http://127.0.0.1:3100/internal/health
# expect sandbox: docker

Missing supervisor token is a setup failure: do not treat sandbox: "none" as success for this path.

Signup and local Docker computers work without an E2B (or other remote) account.

Bot browser

The published computer image uses Chromium. To use Brave instead, build the image with BROWSER=brave and point Compose at it:

docker build --build-arg BROWSER=brave -t rakazo-computer-brave infra/sandboxes/computer
RAKAZO_COMPUTER_IMAGE=rakazo-computer-brave
RAKAZO_COMPUTER_IMAGE_TAG=latest

The image holds one browser, and rakazo-browser launches whichever is installed. A locally built image is not on a registry, so start the stack with bash install-images.sh --pull-never after pulling the other images once. Brave’s Rewards, Wallet, Leo, VPN and News are disabled by managed policy. Browser identity is only one input to bot-check challenges; the host’s IP reputation usually matters more.

kubernetes

Each computer is one pod in a namespace you choose, created from the published computer image. The API and worker talk to the Kubernetes API with their own service account (or the kubeconfig in KUBECONFIG), so they normally run in the same cluster. No supervisor or Docker socket is involved.

Variable Default Notes
KUBERNETES_SANDBOX_NAMESPACE rakazo-sandboxes Namespace for computer pods; it must exist
KUBERNETES_SANDBOX_COMPUTER_IMAGE ghcr.io/elie222/rakazo/computer:edge Pin a digest in production
KUBERNETES_SANDBOX_RUNTIME_CLASS_NAME unset For example kata or gvisor for a VM or user-space kernel boundary
KUBERNETES_SANDBOX_CPU_LIMIT / _MEMORY_LIMIT unset Container limits, such as 2 and 4Gi
KUBERNETES_SANDBOX_POD_LABELS / _POD_ANNOTATIONS unset JSON objects of strings, merged into every pod
KUBERNETES_SANDBOX_READY_TIMEOUT_MS 120000 How long a new pod has to become ready

Each pod runs the image’s own user (uid 1000) with no service account token and no service links, and is labelled app.kubernetes.io/name=rakazo-computer and rakazo-bot-id=<bot id>. Commands, file transfers and workspace save and restore go through pods/exec; save and restore stream the home as one tar archive. Screens use the image’s desktop gateway on the pod IP, so the API, the worker and the web screen proxy must be able to reach pods in that namespace on port 6080 and the display ports from 6100 upward. Pods are ephemeral: the saved workspace is the persistence boundary, as with the remote providers. Kubernetes computers do not bill while running, so COMPUTER_MAX_ACTIVE_MS does not apply to them.

Mounted home (optional)

By default each computer’s home is copied in when it starts and saved back when it stops. Set KUBERNETES_SANDBOX_HOME_CLAIM to the PersistentVolumeClaim that holds the API’s DATA_DIR to mount homes/<home key> from it at the workspace instead, so the home is never copied, like a local Docker computer. The claim must be in the computer namespace, so in practice the computers run in the API’s namespace. Because a ReadWriteOnce volume is attached to one node, KUBERNETES_SANDBOX_HOME_COLOCATE_NAMESPACE and KUBERNETES_SANDBOX_HOME_COLOCATE_LABELS (a JSON object) are required with it and become a required pod affinity to the pods that hold the volume, for example {"app":"rakazo-api"}. The pod gets fsGroup: 1000. An existing pod without the home volume keeps copying its files until it is stopped; its next fresh pod mounts the saved home. This applies only to the local home store; with OBJECT_STORAGE_BUCKET set, the home claim is ignored and homes are copied as usual.

The API and worker service account needs this in the computer namespace:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: rakazo-computers
  namespace: rakazo-sandboxes
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["create", "get", "delete"]
  - apiGroups: [""]
    resources: ["pods/exec"]
    verbs: ["create", "get"]

Bind it to the service account the API and worker run as with a RoleBinding. A NetworkPolicy that admits only the API, worker and web pods to the computer pods, and denies their egress to private ranges, is recommended.

none

Runs API/web without provisioning bot computers.

Remote providers

Set SANDBOX_PROVIDER to exactly one of:

Value Credential Notes
e2b E2B_API_KEY Hosted sandboxes
novita NOVITA_API_KEY Build the desktop template first; optional SANDBOX_TEMPLATE
daytona DAYTONA_API_KEY Optional DAYTONA_API_URL, DAYTONA_TARGET, DAYTONA_SNAPSHOT
boat BOAT_API_KEY Optional BOAT_API_URL (see .env.example)
boxd BOXD_API_KEY Optional BOXD_ORG to create computers in a boxd organization

Remote paths still need a working API/worker; they do not replace Postgres or the web UI. They require egress to the provider. For air-gapped hosts prefer docker with pre-loaded images, or none.

A run starts its computer only when it first uses a computer tool. With idle sleep on, a computer suspends after SANDBOX_IDLE_MS idle. On providers that bill while running (E2B, Daytona, Boat, boxd, CreateOS, Railway), it also suspends after COMPUTER_MAX_ACTIVE_MS (default 2 hours) up in one stretch, even with background work or an open screen, unless a run is executing on it or a user is in control of its screen. 0 turns the cap off. That exception lifts only the uptime cap; idle suspension after SANDBOX_IDLE_MS still applies once nothing holds the computer. Docker and this-Mac computers are never capped. The next use resumes it. Asking for takeover restarts the idle timer, and on providers that do not bill while running the run also holds the computer for 30 minutes after asking, so the user can answer the push on the same open page.

Computer run time

Each stretch a computer runs at its provider is a row in computer_runs, attributed to the computer’s owner and space. A run starts when the computer becomes ready (boot time is not counted) and ends when it stops. endReason says how: stopped, idle, active_limit, replaced (update, recovery, or reset), gone (the provider had already stopped or lost the sandbox), deleted, or reconciled (the end was never seen; the worker found the computer no longer running). For gone and reconciled, endedAt is when Rakazo noticed, so the true end may be earlier. The worker also opens the run of a running computer whose start was not recorded. An open run whose computer still reads as running is a computer that may be left on. Deleting a user or space keeps their runs with userId and spaceId cleared. This-Mac computers are not recorded.

The table has row-level security, so query it as the database owner. Timestamps are UTC. Run hours per user over the last 7 days, counting runs still open up to now:

WITH now_utc AS (SELECT now() AT TIME ZONE 'UTC' AS at)
SELECT r."userId", u.email,
       round(sum(extract(epoch FROM
         coalesce(r."endedAt", n.at) - greatest(r."startedAt", n.at - interval '7 days')
       )) / 3600, 1) AS hours
FROM computer_runs r
CROSS JOIN now_utc n
LEFT JOIN "user" u ON u.id = r."userId"
WHERE coalesce(r."endedAt", n.at) > n.at - interval '7 days'
GROUP BY r."userId", u.email
ORDER BY hours DESC;

Runs open longer than 6 hours:

SELECT r."computerId", r."userId", r."spaceId", r.kind, r."startedAt",
       round(extract(epoch FROM (now() AT TIME ZONE 'UTC') - r."startedAt") / 3600, 1) AS hours_open
FROM computer_runs r
WHERE r."endedAt" IS NULL
  AND r."startedAt" < (now() AT TIME ZONE 'UTC') - interval '6 hours'
ORDER BY r."startedAt";

Linux background-work inspection

The fallback background-work probe and cancellation commands require Bash, Python 3.7 or newer, and a readable/searchable /proc root. Both supplied images install python3 explicitly in infra/sandboxes/computer/Dockerfile and infra/sandboxes/desktop/Dockerfile. Custom and remote images must provide this capability. The scanner itself never installs Python.

The scanner reads marker file descriptors regardless of the holder’s UID, including duplicated, inherited, and deleted-but-open markers. Individual entries that disappear or deny access are skipped because their links cannot be observed. A failed root traversal, unexpected filesystem error, or missing Python capability makes inspection unknown. Such failures do not authorize marker cleanup or cancellation signals from partial scanner output. Cancellation rechecks each selected PID’s marker descriptors before signaling it.

E2B computer template

E2B computers start from the rakazo-computer template: E2B’s stock desktop at 2 vCPU and 4 GB instead of its 8 vCPU and 8 GB. Build it once in the E2B team that owns E2B_API_KEY, and again whenever its definition in packages/adapters/src/e2b-template.ts changes:

E2B_API_KEY=<deployment key> pnpm sandbox:e2b-template

Until it exists, new computers use the stock desktop template. Existing computers keep the size they were created with until they are replaced.

Novita computer template

Set SANDBOX_PROVIDER=novita and NOVITA_API_KEY on both the API and worker. Build the desktop template once in the account that owns that key:

NOVITA_API_KEY=<deployment-key> pnpm sandbox:novita-template

The rakazo-computer template uses Novita’s desktop image at 2 vCPU and 4 GB. The provider requires this template and does not fall back to the larger stock desktop. To use another desktop template, set SANDBOX_TEMPLATE to the same name when building and on both services. Rebuild when the template definition changes; existing computers keep their original template and resources.

Computers pause on idle timeout and on stop, preserving files, browser profiles, and running-process memory. Reconnecting resumes the same computer. Destruction deletes it without resuming it. Stop retries pause requests and checks the remote state. Command cancellation and deadlines retry termination; if termination cannot be confirmed, the adapter attempts to pause the computer and reports that the command may remain suspended. An unconfirmed command stop also quarantines the computer in the shared database, so API heartbeats, worker jobs, and restarts cannot silently resume it. Use the existing Recover or Reset action to replace it from saved work; the paused guest is preserved until that explicit action. Normal reads and heartbeats do not resume a paused Novita computer. An outage can prevent both operations; the configured idle timeout remains the final auto-pause boundary. This recovery does not delete the saved computer. Paused storage remains subject to Novita’s storage billing and retention terms. This provider uses the shared Linux desktop runtime for screens, takeover, terminal, and portable workspace transfer.

A missing key disables computers without preventing startup. A present but invalid key, a missing template, or a quota failure surfaces when a computer starts; an authentication or network error does not authorize replacing an existing computer.

Boat computer snapshot

Boat computers start from a named snapshot, rakazo-computer-<hash>, that already has the desktop tools installed. The hash follows the setup in packages/adapters/src/boat-sandbox.ts, so changing it produces a new name and existing computers keep the one they started from.

No setup is needed: when the snapshot for the current setup is missing, the API or worker logs sandbox.base_image with outcome: "missing", builds it once in the background from a fresh sandbox with no account environment and no bot data, and deletes the build sandbox. Until it is ready, new computers install the tools themselves. To build it ahead of the first computer, in the Boat account that owns BOAT_API_KEY:

BOAT_API_KEY=<deployment key> pnpm sandbox:boat-snapshot

boxd computers

boxd computers are isolated microVMs: no boxd credentials inside, and no route to the account’s other machines. They start from a snapshot, rakazo-computer-<hash>, with the desktop tools and Chrome installed, built the same way as Boat’s: once in the background when missing, from a fresh machine that is deleted afterwards. To build it ahead of the first computer:

BOXD_API_KEY=<deployment key> pnpm sandbox:boxd-snapshot

The desktop is served on the machine’s own public HTTPS address; every screen and terminal still needs its own token, which the desktop gateway checks. Stopping a computer suspends it with its memory, so programs it was running continue when it resumes.

Verify a provider change

After changing provider or keys (from the published-images drop directory that holds docker-compose.images.yml and .env):

docker compose --env-file .env -f docker-compose.images.yml up -d
curl -fsS http://127.0.0.1:3100/internal/health

Confirm sandbox equals the intended provider (e2b, novita, daytona, boat, or boxd). HTTP 200 alone does not verify a remote provider: a missing API key falls back to sandbox: "none". A present but invalid key still reports the selected provider. Open a bot’s computer to verify provisioning and desktop access.