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.