Skip to main content
This guide configures the optional sandbox thread runner on an existing Google Kubernetes Engine (GKE) cluster. Each selected Genesis chat thread runs its Claude Code agent in a separate Pod, while Genesis provides the LLM proxy, MCP endpoint, and thread transcript.
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 kubectl access and a Genesis Helm deployment.
  • Helm 3.8 or later and the Genesis chart package or OCI chart.
  • The Genesis backend running in the genesis namespace. The supplied manifest assumes the backend uses the default ServiceAccount and that the genesis Service exposes FastAPI on port 8080.
  • A registry image built from container/Dockerfile.sandbox-claude and reachable from the GKE cluster. See Genesis on Docker — Sandbox Claude Code runner image.
Verify cluster access before applying the platform resources:
An empty ServiceAccount value means 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 includes gcp-gke/sandbox-runner-platform.yaml. Apply it once per cluster, before enabling the runner:
The manifest creates:
  • The genesis-sandbox namespace.
  • Role-based access for the Genesis backend to create, inspect, execute in, and delete runner Pods, plus create and remove per-thread PersistentVolumeClaims.
  • A ResourceQuota limiting the namespace to 25 Pods and 40 PersistentVolumeClaims.
  • A NetworkPolicy that denies ingress and permits egress only to DNS and the Genesis backend on TCP port 8080. The backend fronts both the sandbox LLM proxy and MCP endpoint.

Enable the runner

Set the existing sandbox_runner settings in Advanced Settings, your genesis-config.yaml, or the advanced config API. The minimum GKE values are:
The feature remains disabled by default (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 — set cliTerminal.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:
That creates the 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).
Each workspace Pod reserves 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:
The feature still requires the 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:
The manifest is designed for the agent’s unrestricted execution inside the Pod; the Pod boundary and the NetworkPolicy provide the cluster-side containment. A cold start on GKE Autopilot can include node provisioning, so the default 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’s sandbox-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

After a selected bot starts a chat thread, the runner creates a thread Pod and a thread-scoped PVC in 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.