Self-host a single-tenant SuperAgent on your own Kubernetes cluster. The chart deploys one long-lived host-app pod that orchestrates per-agent pods via the in-cluster Kubernetes API.
namespace
├── Deployment host-app (1 replica, SQLite single-writer)
│ └── creates/deletes ──▶ Pod agent-<id> + Service agent-<id>
├── Service + Ingress → host-app UI/API
├── PVC (shared, RWX) ← host-app data dir + agent workspaces
│ backend: any POSIX-compliant RWX provider
│ e.g. NFS · Longhorn · CephFS · AWS EFS · S3 Files (efs.csi) · Azure Files · Filestore
├── ServiceAccount + Role/RoleBinding (pods/services CRUD)
└── NetworkPolicy (agent ingress confined to host-app)
The host-app and every agent pod share one PVC: the runtime mounts each agent's
workspaces/<id>/workspace subPath into the agent pod. The backend can be any
POSIX-compliant RWX provider — NFS, Longhorn, CephFS, AWS EFS, S3 Files (via the
efs.csi.aws.com driver), Azure Files, GCP Filestore, etc. SuperAgent runs SQLite,
git and npm in the workspace, so the volume must support real POSIX semantics
(random writes, fcntl locking), not plain object storage.
On a multi-node cluster the PVC must be ReadWriteMany so host-app and agent
pods can mount it from different nodes. A single-node cluster can use ReadWriteOnce.
For a dedicated PVC, leave storage.subPath empty; host-app owns the whole
claim. When multiple installs share one backing filesystem, set
storage.subPath so each install sees only its own data directory.
Some RWX backends require the parent directory to already exist before Kubernetes
can mount a nested subPath. In that case, set storage.subPathParent to an
existing writable parent and storage.subPath to the child directory; the chart
will create the child directory during init:
storage:
existingClaim: shared-workspaces
subPathParent: tenants
subPath: tenants/acme/superagent-data
agent:
workspacesSubPathPrefix: tenants/acme/superagent-data/agentsThe chart also runs a permissions init step. Some managed filesystems ignore
chown, so the init uses broad write permissions for the mounted data directory.
Before installing, have these ready:
- A Kubernetes cluster (1.24+) and
kubectl+helmconfigured against it. - A
ReadWriteManyStorageClass (NFS, Longhorn, EFS, …) for multi-node clusters. A single-node cluster can useReadWriteOnce(setstorage.accessMode=ReadWriteOnce). - A way to reach host-app. By default the chart only creates a
ClusterIPService — verify withkubectl port-forward(no public exposure). Exposing it publicly is your cluster's job; see Exposing host-app. - An LLM provider API key when you are ready to run agents. Self-host defaults to Anthropic — get a key from https://console.anthropic.com → API Keys. You can provide it during install, or add it later in the UI.
hostApp.settings.llmProvider selects the provider. You can either set the
matching key in secrets.env (or your existingSecret) during install, or add
it later from the host-app UI:
llmProvider |
Secret key to set | Where to get it |
|---|---|---|
anthropic (default) |
ANTHROPIC_API_KEY |
console.anthropic.com |
openrouter |
OPENROUTER_API_KEY |
openrouter.ai/keys |
bedrock |
AWS_BEARER_TOKEN_BEDROCK |
AWS Bedrock console |
Helm does not read a .env file. You can start without an LLM key, sign in
as the first admin, and add the key in the UI before starting agents.
If you want the key available at first boot, provide it one of two ways:
Install from a local clone of this repo (
git clone … && cd k8s-runtime-helm). The commands below use the chart path./k8s-runtime-helm; an OCI-published chart will come later.
Option A — let the chart create the Secret (simplest). Pass keys via
secrets.env; the chart renders a Kubernetes Secret and wires it into host-app
with envFrom:
helm install superagent ./k8s-runtime-helm \
--namespace superagent --create-namespace \
--set hostPublicUrl=https://superagent.example.com \
--set storage.storageClassName=<your-rwx-class> \
--set-string secrets.env.ANTHROPIC_API_KEY=sk-ant-...Avoid putting real keys in a values file you commit. Use
--set-stringon the CLI, or Option B.
Option B — reference a Secret you created (keeps keys out of Helm entirely):
kubectl create namespace superagent
kubectl -n superagent create secret generic superagent-secrets \
--from-literal=ANTHROPIC_API_KEY=sk-ant-...
helm install superagent ./k8s-runtime-helm \
-n superagent \
--set hostPublicUrl=https://superagent.example.com \
--set storage.storageClassName=<your-rwx-class> \
--set secrets.existingSecret=superagent-secretsThe Secret's keys become env vars on host-app verbatim, so name them exactly as
the provider expects (e.g. ANTHROPIC_API_KEY).
By default the chart creates a ClusterIP Service, so host-app stays reachable
inside the cluster without being exposed publicly. Public exposure depends a lot
on your setup (ingress controller, load balancer, gateway, DNS, TLS), so the chart
leaves that choice to you.
Access it during setup / testing:
kubectl -n superagent port-forward svc/superagent 8080:80
# then open http://localhost:8080When you want public access, pick whatever fits your cluster:
- Point your existing ingress controller at the
superagentService, or - Set
service.type=LoadBalancerto get a cloud load balancer, or - Enable the example Ingress and fill in settings for your controller:
ingress:
enabled: true
className: alb # or nginx / traefik / … — no default
host: superagent.example.com
annotations: # provider-specific; e.g. for AWS ALB:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
tls:
enabled: true
secretName: superagent-tlsThe bundled Ingress is just a convenience template that works with any
controller. See ci/eks-alb-efs-values.yaml for a
full AWS ALB example.
auth.mode defaults to true — host-app runs as a multi-user deployment with
login. No OIDC setup is required: it uses email/password (local auth) out of
the box. The first user to sign up becomes the admin; by default further
signups are invitation-only and need admin approval (tunable in host-app's auth
settings).
Optionally, add OIDC/social login by setting AUTH_PROVIDERS_JSON. It carries
a clientSecret, so put it in secrets.env or your existingSecret, never in a
committed values file:
secrets:
env:
ANTHROPIC_API_KEY: sk-ant-...
AUTH_PROVIDERS_JSON: |
[
{
"id": "google",
"type": "oidc",
"displayName": "Google",
"discoveryUrl": "https://accounts.google.com/.well-known/openid-configuration",
"clientId": "<your-oidc-client-id>",
"clientSecret": "<your-oidc-client-secret>",
"scopes": ["openid", "email", "profile"]
}
]Notes:
- OIDC redirect/callback URLs are derived from
hostPublicUrl— register that origin in your OIDC app. - Enabling auth requires a clean data directory on first boot. Enabling it on a PVC that already holds non-auth agents is rejected at startup.
- Running a single-user instance with no login? Set
auth.mode=false.
| Key | Default | Notes |
|---|---|---|
hostPublicUrl |
"" |
Required. External URL of the host-app. |
auth.mode |
true |
Multi-user login (email/password by default). false = single-user. |
storage.existingClaim |
"" |
Bring-your-own PVC; chart skips creating one. |
storage.subPath |
"" |
Optional host-app data subPath when sharing one backing filesystem. |
storage.subPathParent |
"" |
Existing parent subPath used to create a nested storage.subPath. |
storage.storageClassName |
"" |
RWX-capable class; "" = cluster default. |
storage.accessMode |
ReadWriteMany |
ReadWriteOnce only on single-node. |
secrets.env |
{} |
Provider keys injected as env (e.g. ANTHROPIC_API_KEY). |
secrets.existingSecret |
"" |
Reference a pre-created Secret instead. |
ingress.enabled |
false |
Example Ingress; off by default. Expose host-app yourself. |
ingress.className |
"" |
Your ingress controller class (alb / nginx / …). No default. |
agent.resources |
cpu: 1, memory: 2Gi |
Per-agent pod resources. |
image.hostApp.tag / image.agentContainer.tag |
"" |
Default to chart appVersion. |
networkPolicy.enabled |
true |
Confine agent ingress to host-app. |
See values.yaml for the full list.
hostApp.replicasis pinned to1; the chart refuses to render with more (host-app is a single-writer over the shared PVC).settings.jsonis seeded only on first boot; later changes tohostApp.settingswon't re-apply (host-app persists its own settings).- Agent pods are garbage-collected via an
ownerReferenceto the host-app pod: if host-app dies, the cluster cleans up orphaned agent pods.