This is the
sandbox_runner.runtime: kubernetes runner. It is separate from the
chart’s older sandbox.enabled backend. The runner is off until you explicitly
enable sandbox_runner.enabled and select at least one bot.For a full base install of Genesis on a new GKE cluster with Helm — preflight and
grants, images and chart, the web console, and the on-demand per-user sandbox
terminal — see Installing Genesis on GKE with Helm.
Prerequisites
- An existing GKE cluster with
kubectlaccess and a Genesis Helm deployment. - Helm 3.8 or later and the Genesis chart package or OCI chart.
- The Genesis backend running in the
genesisnamespace. The supplied manifest assumes the backend uses thedefaultServiceAccount and that thegenesisService exposes FastAPI on port8080. - A registry image built from
container/Dockerfile.sandbox-claudeand reachable from the GKE cluster. See Genesis on Docker — Sandbox Claude Code runner image.
default, which is the manifest’s assumption.
If your deployment uses another namespace, ServiceAccount, Service name, or port,
update the manifest before applying it.
Apply the GKE platform manifest
The Helm chart includesgcp-gke/sandbox-runner-platform.yaml. Apply it once per
cluster, before enabling the runner:
- The
genesis-sandboxnamespace. - Role-based access for the Genesis backend to create, inspect, execute in, and delete runner Pods, plus create and remove per-thread PersistentVolumeClaims.
- A
ResourceQuotalimiting the namespace to25Pods and40PersistentVolumeClaims. - A
NetworkPolicythat denies ingress and permits egress only to DNS and the Genesis backend on TCP port8080. The backend fronts both the sandbox LLM proxy and MCP endpoint.
Enable the runner
Set the existingsandbox_runner settings in Advanced Settings, your
genesis-config.yaml, or the advanced config API. The minimum GKE values are:
sandbox_runner.enabled: false), and an
empty sandbox_runner.bot_ids list routes no bot by default. For the complete
runner settings, per-thread override, and lifecycle behavior, see Sandbox thread
runner.
Enable the Kubernetes CLI terminal
The direct CLI terminal can use the same GKE cluster with a separate namespace and quota. Unlike the sandbox thread runner, it creates one long-lived workspace Pod per user and keeps that user’s two PersistentVolumeClaims after the Pod is reaped. The chart renders the cluster objects and switches the feature on together — setcliTerminal.enabled=true in your values file. The GKE Helm install
runbook
covers the chart’s force-locked runtime values and the interaction with Advanced Settings:
genesis-cli namespace, grants the ServiceAccount the backend actually
runs as (default unless serviceAccount.create/.name is set) permission to manage
workspace Pods, Pod exec connections, and PersistentVolumeClaims, adds the ResourceQuota,
and sets plus force-locks GENESIS_CLI_TERMINAL_ENABLED, _RUNTIME and _K8S_NAMESPACE
on the backend. cliTerminal.maxWorkspaces is rendered as an integer in both the quota and
the backend environment, including when Helm receives a value such as 10.0.
The default cli_terminal.home_volume_size is 10Gi for each claim, so plan for 20Gi
per user. Claims are not reclaimed automatically; an operator must retire both claims for
a departing user.
The
genesis-cli namespace survives both helm uninstall and
cliTerminal.enabled: false. It holds every user’s home volume and therefore their
saved conversations, so it carries helm.sh/resource-policy: keep — otherwise turning
the feature off would delete their work as a side effect. Switching it off revokes the
RBAC and releases the quota, but does not force the application’s own setting to false;
deployments with an externally provisioned namespace and RBAC can keep working. A
chart-managed deployment should treat the missing grants as disabled until the chart
switch is turned back on. It does not stop agents already running,
because the Pods are retained with the namespace and the reaper goes with the backend.
Two explicit, separate commands: cluster/scripts/drain-workspaces.sh <release> (Pods
go; volumes, namespace and release all stay) and cluster/scripts/teardown.sh <name> --delete-workspaces (tears the instance down and destroys the volumes).cli_terminal.memory_request (default 4g → 4Gi) and
may grow to the cli_terminal.memory ceiling (default 8g → 8Gi). The ceiling is a
hard cap: exceeding it OOM-kills the container, which kills every session that user has
open. The request is what the scheduler reserves and therefore what cluster capacity must
supply — plan cluster capacity at 4Gi per concurrent user, with headroom to burst to
8Gi. (The chart’s ResourceQuota counts Pods and claims, not memory, so this is capacity
to provide rather than a quota to set.) No CPU limit is set (CPU is compressible), so cli_terminal.cpu_request (500m) is
the only CPU number the scheduler sees.
A 1Gi memory-backed /dev/shm (cli_terminal.shm_size) is mounted for the headless
Chrome the agent drives, replacing the runtime’s 64Mi default. Its used pages are charged
against the memory ceiling as the shm fills (Kubernetes does not pre-reserve the
sizeLimit), so a full shm can leave up to 1Gi less for the process — and it needs no
share of the request.
Application-level settings can also be set in Advanced Settings, genesis-config.yaml, or
the advanced config API:
use_cli_terminal role permission. Workspace egress is
unrestricted by default — a workspace has the same network access as any other Pod in
the cluster, which is deliberate for a development workspace that has to clone
repositories and install packages. cliTerminal.networkPolicy.enabled=true denies ingress
entirely and confines egress to DNS plus the Genesis backend on the backend’s container
port 8080, which fronts the LLM proxy and MCP endpoint; git/gh, npm and pip then
time out. A NetworkPolicy is only as good as your CNI — GKE enforces it, a stock EKS
cluster with the default VPC CNI was measured enforcing nothing — so verify rather than
assume.
If your platform objects are applied outside Helm, render them from the same template
rather than keeping a hand-edited copy:
Optional gVisor isolation
On GKE, you can run runner Pods under gVisor:sandbox_runner.k8s_pod_ready_timeout_seconds: 420.0 accommodates measured
169–187-second starts when the sandboxed node pool has scaled to zero. If the
configured runtime or image cannot be used, Genesis hides the Claude Code picker and
falls back to the normal Genesis harness rather than failing the turn at routing time.
Avoid Helm ownership conflicts
The chart’ssandbox-rbac.yaml also creates a genesis-sandbox namespace when
sandbox.enabled: true and sandbox.createSharedResources: true. Do not enable
that older backend while this manifest owns the namespace: a later Helm upgrade can
fail because the namespace lacks Helm ownership metadata. Leave sandbox.enabled
at its default false, or transfer ownership to Helm before upgrading:
sandbox.createSharedResources: false alone does not resolve the conflict because
the chart still renders bindings for resources that this manifest does not define.
Verify the runner resources
genesis-sandbox. The quota and policy should remain in place
for the lifetime of the deployment. The Pod limit bounds concurrent runner threads,
while PVCs can outlive their Pods so an idle thread can be resumed with its context.
The manifest therefore allows more PVCs than Pods; when the PVC ceiling is reached,
the runner reclaims the oldest idle volume before creating storage for a new thread
instead of returning a bare 403.
