Skip to main content
The official Helm chart deploys the all-in-one Agent Canvas image (frontend + agent-server + automation) on Kubernetes as a StatefulSet with a PersistentVolumeClaim for durable state, a Service, and an optional Ingress and RBAC layer. It’s the recommended way to run Agent Canvas as a shared, always-on backend that survives pod restarts and image upgrades.
Turn this into an internal vibecoding platform. Once Agent Canvas runs in your cluster with RBAC enabled, you can give it a skill that teaches the agent how to deploy the small web apps it builds straight into the cluster. From that point on, anyone with access to the Agent Canvas UI can build and ship code into the cluster — and save it to GitHub — with just a prompt. No pipelines, no manual kubectl, no hand-written manifests: the agent scaffolds the app, applies the manifests, and gives back a live URL.The “save to GitHub” half of that loop requires the GitHub MCP server to be enabled so the agent can create repos and push commits on the user’s behalf. See the generic app-deployment skill below for a ready-to-adapt version, and RBAC for the permissions it needs.
The agent server can read and write the pod filesystem, execute shell commands, and — when RBAC is enabled — mutate the Kubernetes cluster it runs in. Treat the release namespace as trusted infrastructure, put it behind an authenticated ingress before exposing it to the internet, and only turn on rbac.clusterAdmin when you truly need cluster-wide access.

Relationship to OpenHands Enterprise

Agent Canvas is an unauthenticated, single-tenant application. This Helm chart runs exactly that: one shared instance where all agents are comingled on the same pod and PVC, with no built-in authentication, user-level role-based access control, or tenant isolation. It’s a good fit for a single team or individual running their own backend, and for the “internal vibecoding platform” pattern described above — where a small, trusted group shares one deployment. OpenHands Enterprise (OHE) is the productized upgrade path when you need a hardened, multi-user deployment. OHE adds:
  • Authentication (SSO / SAML / OIDC) so users must log in before they can run agents.
  • Role-based access control over who can run agents and manage the deployment.
  • Multi-tenancy so different teams get isolated spaces rather than one shared instance.
  • Isolated agent sandboxes — each agent run executes in its own container instead of every agent sharing the pod’s filesystem and shell.
Use this Helm chart for self-hosted, single-tenant setups; reach for OHE when you need authentication, multi-tenancy, or isolated agent execution.

Prerequisites

  • Kubernetes 1.24 or later (required by the chart’s kubeVersion constraint).
  • Helm 3.x.
  • A working kubectl context with permission to create resources in the target namespace.
  • A StorageClass that supports ReadWriteOnce volumes. On GKE this is usually standard-rwo on older node pools or hyperdisk-balanced on c4 / n4 node pools. On EKS it’s gp3. On DigitalOcean/Linode it’s do-block-storage / linode-block-storage.
  • An ingress controller (nginx, Traefik, cloud-provider ingress, etc.) if you want to reach Agent Canvas from outside the cluster.

Get the Chart

The chart lives alongside the source in the OpenHands/agent-canvas repo. Clone it and install from the local path:
That single command deploys everything below. Agent Canvas is now reachable inside the cluster at http://agent-canvas.agent-canvas.svc.cluster.local:8000. See Access It for how to reach it from a browser.

What Gets Deployed

Persistence

The chart provisions one PVC and mounts it at multiple well-known subdirectories of the openhands user’s HOME via subPath. That preserves the pristine /home/openhands the base image ships (dotfiles like ~/.bashrc and ~/.profile) while persisting the directories that actually contain state:
  • ~/.openhands — agent-server settings and encrypted secrets, conversation history and event stores, automation SQLite database (unless you point at external Postgres — see External Database), the OH_SECRET_KEY and session API key auto-generated on first boot
  • ~/workspace — the agent’s default working directory: cloned repos, worktrees, anything the agent writes when it treats ~ as the workspace root
Both paths share the same underlying disk. Add more entries to persistence.mounts if you want other subtrees persisted (e.g. ~/.cache, ~/.config). Defaults:
The pod runs as openhands (UID 10001) from the upstream image. The chart sets podSecurityContext.fsGroup: 10001 so the kubelet chowns the PVC on mount and the process can write to it. If you override securityContext or podSecurityContext, make sure UID/GID/fsGroup all point at the same user or openhands won’t be able to write to the volume.
On GKE clusters using c4 or n4 node pools, the default standard-rwo StorageClass will fail to attach because those machine types require hyperdisk-balanced. Set persistence.storageClassName: hyperdisk-balanced explicitly.

Bring Your Own PVC

If you already manage the volume out of band, point the chart at it and it will skip the volumeClaimTemplates path:

Ingress

Ingress is off by default. Enable it and provide the standard knobs — the chart supports className, annotations, multiple hosts with per-path routing, and TLS.
The read/send timeout annotations matter — Agent Canvas holds long-lived WebSocket connections for streaming agent events. Without generous timeouts, the ingress controller will close idle streams and the UI will drop reconnects mid-turn. Nginx defaults to 60 seconds.

RBAC

RBAC is off by default. The pod runs under its own ServiceAccount but has no in-cluster permissions. Turn it on when the agent needs to inspect or mutate Kubernetes resources (e.g. to deploy things it builds). Two independent switches:
rbac.clusterAdmin: true grants cluster-admin — the highest privilege level in Kubernetes. An agent with a compromised prompt (or a bad LLM response) could delete every resource in the cluster. Prefer scoping to specific namespaces with rbac.namespaces whenever possible.

Skill: Deploying Apps Into the Cluster

To unlock the internal vibecoding platform described at the top of this page, give the agent a skill that teaches it the conventions for shipping the apps it builds into a namespace of your cluster. Drop the markdown below into .openhands/skills/deploy-app/SKILL.md (or your workspace’s skills directory), adjust the placeholders (<namespace>, <domain>, GitHub org), and the agent will scaffold, deploy, and — with the GitHub MCP server enabled — push each app to its own repo on request. This is a generic version of the skill the OpenHands team runs internally. It assumes the backend was installed with rbac.enabled=true and a rbac.namespaces entry for the target namespace, so the pod’s ServiceAccount can kubectl apply there directly.

Common Configurations

Minimal (defaults + ingress)

With LLM Credentials from a Secret

Rather than typing your LLM key into the UI on every reinstall, pass it in through the chart. Create the secret separately, then reference it via config.extraEnv:

Agent That Manages a Sandbox Namespace

Create the sandbox namespace before installing (kubectl create namespace agent-sandbox). Then the pod can kubectl apply / kubectl delete anything inside agent-sandbox but nothing else.

External Database

The automation subsystem uses a SQLite database on the PVC by default. For higher-volume deployments, point it at Postgres:
Store the actual credentials in a Kubernetes Secret and reference them via config.extraEnv rather than putting the password in values.yaml.

Install and Upgrade

To pin a specific image (e.g. a PR preview or a build newer than the chart’s appVersion):

Access It

The chart’s default Service is ClusterIP. Three common ways to reach the UI:
  1. Ingress — configure the ingress: block as shown above. This is the production path.
  2. Port-forward — for quick access from your laptop without touching DNS or ingress:
    Then open http://localhost:8000.
  3. LoadBalancer — set service.type: LoadBalancer if your cloud provisions cloud load balancers for you. Cheaper than ingress for one-off installs, but skips TLS and auth.
The agent server accepts any request with the right LOCAL_BACKEND_API_KEY, so exposing it via a bare LoadBalancer means anyone on the internet who can guess the key can drive the agent. Prefer the Ingress path with an authenticated proxy (oauth2-proxy, Cloudflare Access, tailscale-serve, ngrok OAuth, etc.) in front of it.

Uninstall

The PVC created by the StatefulSet is retained on uninstall so a reinstall picks up where you left off. Delete it explicitly if you want a fully clean slate:

Troubleshooting

FailedAttachVolume: pd-balanced disk type cannot be used by c4-standard-8 machine type

The default StorageClass on your cluster is provisioning a disk type your nodes can’t attach. On GKE c4 / n4 node pools, use hyperdisk-balanced:
Because volumeClaimTemplates on an existing StatefulSet are immutable, changing the StorageClass requires deleting the STS and PVC first:

ErrImagePull on ghcr.io/openhands/agent-canvas:<tag>

Verify the tag exists on GHCR — the chart’s appVersion pins the default. To pull an image built from a specific commit, use --set image.tag=sha-<short-sha>. See GHCR for the tag list.

WebSocket disconnects every minute

Your ingress is closing idle streams. Bump the timeout annotations on the Ingress:

Pod stuck in Pendingno persistent volumes available

Either no StorageClass exists on the cluster, or the one you set doesn’t provision on demand. Run kubectl get storageclass and set persistence.storageClassName to one that shows VOLUMEBINDINGMODE=WaitForFirstConsumer (Immediate is fine too).