Skip to main content
Version: v2.0

Deploy on Kubernetes

Community-Maintained Beta

The Agenta Helm chart is community-maintained and currently in beta. If you encounter issues or have suggestions, please open a GitHub issue or reach out in our Slack community.

This guide walks you through deploying Agenta on Kubernetes using the Helm chart. By the end, you will have a working Agenta instance running in your cluster.

Database migrations run automatically as a post-install or post-upgrade hook.

Before other people depend on the deployment, work through Prepare a Kubernetes deployment for production. It covers replicas, disruption budgets, rollout settings, and the settings that are required on Google Kubernetes Engine.

Prerequisites​

  • A running Kubernetes cluster (v1.24+)
  • kubectl configured to access your cluster
  • helm CLI (v3.10+) installed
  • An ingress controller running on the cluster. Web, API, and services must be served from the same origin via path-based routing (/, /api, /services); a port-forward to a single Service won't work because the browser issues cross-path requests that fail without an ingress. The chart's defaults assume Traefik (see Set Up Ingress and TLS).
Local testing with Traefik

For local clusters (kind / minikube / Docker Desktop), install Traefik v3 and bind a hostname through /etc/hosts:

helm repo add traefik https://traefik.github.io/charts
helm install traefik traefik/traefik \
--namespace traefik --create-namespace \
--set ingressClass.name=traefik

echo "127.0.0.1 agenta.example.com" | sudo tee -a /etc/hosts

# kind/minikube/Docker Desktop don't give the LoadBalancer Service an
# external IP, so bridge localhost:80 to the Traefik Service:
sudo kubectl -n traefik port-forward svc/traefik 80:80 &

Then set agenta.webUrl: http://agenta.example.com (and matching apiUrl/servicesUrl) in your values file. See Path Prefix Stripping for the StripPrefix middleware Traefik needs.

Quick Start​

1. Clone the Repository​

git clone --depth 1 --filter=blob:none --sparse https://github.com/Agenta-AI/agenta
cd agenta
git sparse-checkout set hosting/kubernetes

2. Copy configuration and edit it​

For OSS:

cp hosting/kubernetes/oss/values.oss.example.yaml hosting/kubernetes/oss/.values.oss.yaml

For Enterprise Edition:

cp hosting/kubernetes/ee/values.ee.example.yaml hosting/kubernetes/ee/.values.ee.yaml

Open the copy and fill in the values you need — at minimum agenta.authKey, agenta.cryptKey, agenta.servicesInternalKey, agenta.runnerToken, and postgres.password, plus the public URLs under agenta.* if you are not using the default local URLs:

openssl rand -hex 32 # use for agenta.authKey
openssl rand -hex 32 # use for agenta.cryptKey
openssl rand -hex 32 # use for agenta.servicesInternalKey
openssl rand -hex 32 # use for agenta.runnerToken
openssl rand -hex 16 # use for postgres.password
warning

Save these secret values in a secure secret manager. You will need them for future upgrades, and there is no way to recover encrypted data if agenta.cryptKey is lost.

agenta:
webUrl: "https://agenta.example.com"
apiUrl: "https://agenta.example.com/api"
servicesUrl: "https://agenta.example.com/services"
authKey: "<openssl rand -hex 32>"
cryptKey: "<openssl rand -hex 32>"
servicesInternalKey: "<openssl rand -hex 32>"
runnerToken: "<openssl rand -hex 32>"

postgres:
password: "<openssl rand -hex 16>"

For Enterprise Edition, keep agenta.license: ee, configure global.imagePullSecrets, and review hosting/kubernetes/helm/LICENSE.ee.

3. Install​

For OSS:

helm install agenta hosting/kubernetes/helm \
--namespace agenta --create-namespace \
-f hosting/kubernetes/oss/.values.oss.yaml

For EE, the chart pulls images from a private GHCR repository (ghcr.io/agenta-ai/internal-ee-*). Kubernetes needs credentials to pull them, so create a docker-registry Secret in the target namespace before installing. The example EE values reference it by name in global.imagePullSecrets:

global:
imagePullSecrets:
- name: ghcr-pull-secret

Create the namespace and the Secret with your EE GHCR username and personal access token (PAT with read:packages):

kubectl create namespace agenta

kubectl create secret docker-registry ghcr-pull-secret \
--docker-server=ghcr.io \
--docker-username=<your-github-username> \
--docker-password=<your-ee-pat> \
--namespace agenta

Then install:

helm install agenta hosting/kubernetes/helm \
--namespace agenta \
-f hosting/kubernetes/ee/.values.ee.yaml

If you change the Secret name, update global.imagePullSecrets[0].name in your values file to match.

info

The chart automatically wires postgres.password to both the application pods and the Bitnami PostgreSQL subchart (via a shared Kubernetes Secret). You only need to set it once.

warning

Never commit your edition-specific values file (hosting/kubernetes/oss/.values.oss.yaml or hosting/kubernetes/ee/.values.ee.yaml) to version control if it contains secrets. The repo's .gitignore already excludes these .values.*.yaml overlays; restrict file permissions too (chmod 600 hosting/kubernetes/oss/.values.oss.yaml). For a fully managed secret lifecycle, use secrets.existingSecret to reference a pre-existing Kubernetes Secret or integrate with an external secrets operator.

4. Verify​

# Watch pods start
kubectl -n agenta get pods -w

# Check the migration job completed
kubectl -n agenta get jobs

Once all pods are running, use port-forwarding for a quick local check:

kubectl port-forward svc/agenta-web 3000:3000 -n agenta

Then open http://localhost:3000 in your browser.

If you want to expose Agenta through a public hostname, see Set Up Ingress and TLS.

What gets deployed

The chart creates the following workloads inside your Kubernetes namespace:

  • Web frontend (Next.js)
  • Web (mobile) frontend, served at /m
  • API backend (FastAPI + Gunicorn)
  • Services backend (FastAPI + Gunicorn)
  • Worker (streams) for stream consumers such as tracing and session records
  • Worker (queues) for queue consumers such as evaluations, webhooks, events, interactions, and triggers
  • Cron for scheduled jobs (supercronic)
  • Agent runner for agent workflow execution (agentRunner.enabled defaults to true)
  • PostgreSQL (Bitnami subchart) with three databases
  • Redis Volatile for caching and pub/sub
  • Redis Durable for queues and persistent state
  • SuperTokens for authentication
  • Alembic migration job (post-install/post-upgrade hook)
  • Ingress resource for routing traffic to web, mobile web, API, and services (when enabled)

The durable object store for agent working directories is not deployed by default. store.enabled is false, and the whole store block does nothing until you set it. Turning it on also deploys a bundled SeaweedFS StatefulSet, because store.seaweedfs.enabled defaults to true.

Reference​

Configuration is done through Helm values. Defaults live in hosting/kubernetes/helm/values.yaml; edition overlays live in hosting/kubernetes/oss/.values.oss.yaml and hosting/kubernetes/ee/.values.ee.yaml.

Core Settings​

ValuePurposeDefault
agenta.licenseEdition to deploy (oss or ee)oss
agenta.webUrlPublic web URLderived from ingress.host
agenta.apiUrlPublic API URLderived from ingress.host, plus /api
agenta.servicesUrlPublic services URLderived from ingress.host, plus /services
global.imagePullSecretsImage pull secrets (Bitnami subchart convention)[]

The three URLs have no fixed default. The chart derives each one from ingress.host when ingress.enabled is true and you set ingress.host yourself. The agenta.local fallback that the Ingress uses is not used for the URLs. The chart uses https when ingress.tls is a non-empty list. Otherwise, or with ingress disabled, set all three yourself. The chart fails the install, with a message naming the keys, if any of the three resolves to empty.

Secrets​

ValuePurposeDefault
secrets.existingSecretName of an existing Secret to use instead of the chart-managed one""
agenta.authKeyAuthorization key (required)""
agenta.cryptKeyEncryption key (required)""
agenta.servicesInternalKeyShared secret proving the Services pod is the platform runtime, so a run can read write-only secret values (required)""
agenta.runnerTokenShared secret between the agent runner and its caller (required)""
postgres.passwordPostgreSQL password (required)""
supertokens.apiKeySuperTokens API key (recommended for production)""
identity.<provider>.{clientId,clientSecret,…}OAuth/OIDC provider credentials (Google, GitHub, Okta, Azure AD, …) — injected into pods as <PROVIDER>_OAUTH_CLIENT_ID/SECRET env varsunset
llm.<provider>LLM provider API keys (openai, anthropic, gemini, …) — injected into pods as <PROVIDER>_API_KEY env varsunset

To use an existing Kubernetes Secret instead of having the chart create one, set secrets.existingSecret to the name of your Secret. It must contain AGENTA_AUTH_KEY, AGENTA_CRYPT_KEY, AGENTA_SERVICES_INTERNAL_KEY, AGENTA_RUNNER_TOKEN, and POSTGRES_PASSWORD.

AGENTA_RUNNER_TOKEN is the one exception. You can leave it out of the Secret if you point the runner at a Secret of its own with agentRunner.auth.tokenSecretRef.

If you enable secret-backed settings such as OAuth, LLM provider keys, SendGrid, Composio, or Turnstile, add those keys to the same Secret too. When secrets.existingSecret is set, the chart does not create or update the Secret for you, and it does not read agenta.authKey, agenta.cryptKey, agenta.servicesInternalKey, agenta.runnerToken or postgres.password. From version 0.122.0 the install notes no longer warn that those values still hold their placeholders, because nothing reads them.

Bundled PostgreSQL needs a matching subchart setting

When secrets.existingSecret is set and the bundled PostgreSQL is enabled (the default), you must also point the Bitnami subchart at the same Secret:

helm install agenta hosting/kubernetes/helm \
-f hosting/kubernetes/oss/.values.oss.yaml \
--set secrets.existingSecret=my-secret \
--set global.postgresql.auth.existingSecret=my-secret

The chart aborts with a clear error if you set only one of the two. If you are using an external PostgreSQL (postgresql.enabled=false), only secrets.existingSecret is required.

caution

When supertokens.apiKey is empty, the SuperTokens instance runs without authentication. Any pod that can reach the SuperTokens service can manage auth data. Set an API key for production deployments.

Access Control​

You can restrict who can sign up and create organizations.

info

Access control settings are available only in Enterprise Edition.

ValuePurposeDefault
agenta.access.allowedDomainsOnly allow sign-ups from these email domains (comma-separated)""
agenta.access.blockedDomainsBlock sign-ups from these email domains (comma-separated)""
agenta.access.blockedEmailsBlock specific email addresses (comma-separated)""
agenta.access.allowedOwnerEmailsOnly these emails can create organizations (comma-separated)""

Email (SendGrid)​

To enable transactional emails (invitations and password resets), configure SendGrid.

ValuePurposeDefault
sendgrid.apiKeySendGrid API key""
sendgrid.fromEmailSender email address""

Configuring Images​

ValuePurposeDefault
api.image.repositoryAPI imageDerived from agenta.license
api.image.tagAPI image tag.Chart.AppVersion
web.image.repositoryWeb imageDerived from agenta.license
web.image.tagWeb image tag.Chart.AppVersion
services.image.repositoryServices imageDerived from agenta.license
services.image.tagServices image tag.Chart.AppVersion

Workers, cron, and Alembic jobs reuse the API image.

If you build your own images or mirror them to another registry, set the image repositories directly in your values file:

api:
image:
repository: registry.example.com/agenta-api-ee
tag: v0.95.1

web:
image:
repository: registry.example.com/agenta-web-ee
tag: v0.95.1

services:
image:
repository: registry.example.com/agenta-services-ee
tag: v0.95.1

When these values are set, they override the edition-based defaults.

Component Toggles and Replicas​

Each component (api, web, webMobile, services, workerStreams, workerQueues, cron, agentRunner, supertokens) supports:

ValuePurposeDefault
<component>.enabledEnable/disable the componenttrue
<component>.replicasNumber of replicas1
<component>.resourcesResource requests/limits{}
<component>.nodeSelectorNode selector{}
<component>.tolerationsTolerations[]
<component>.affinityAffinity rules{}
<component>.envExtra environment variables{}
<component>.service.annotationsAnnotations on that component's Service (api, web, webMobile, services only){}

<component>.env takes plain strings, so it cannot reference a Secret key. Two other keys take raw Kubernetes entries. extraEnv renders explicit env entries after the chart's own variables, so a matching name there wins. envFrom supplies only names that no explicit env entry already defines.

Four components ignore replicas, and one cannot be disabled

cron always runs one pod. It runs supercronic, which fires every schedule in every pod, so the chart ignores cron.replicas. redisVolatile, redisDurable and store.seaweedfs also run one pod each.

webMobile has no enabled switch. The mobile web app always deploys and /m always routes. webMobile.enabled: false fails schema validation.

agentRunner.replicas is read, but do not raise it. The API does not route a session to the runner pod that owns it. See Prepare a Kubernetes deployment for production.

Availability and Rollout​

The chart renders a PodDisruptionBudget for every workload, maxUnavailable: 1 for all of them, and topologySpreadConstraints for each workload that runs two or more replicas. A workload with one replica gets no constraints. The chart also sets rollout and shutdown defaults per workload, whatever its replica count. Prepare a Kubernetes deployment for production explains what each one does, what the defaults give you, and what they do not.

ValuePurposeDefault
podDisruptionBudgets.enabledRender a budget per workloadtrue
topologySpread.enabledRender spread constraints per workloadtrue
<workload>.pdb.maxUnavailableReplace the generated budget1
<workload>.pdb.minAvailableReplace the generated budgetunset
<workload>.topologySpreadConstraintsReplace the generated constraint listgenerated on 2 or more replicas
<workload>.strategyRendered verbatim under the Deployment's spec.strategyper workload
<workload>.terminationGracePeriodSecondsRendered on the pod specper workload
<workload>.lifecycleRendered verbatim under the container's lifecycleper workload
<workload>.livenessProbeRendered verbatim as the container's liveness probe (cron, workerStreams, workerQueues only)none

<workload> is one of api, web, webMobile, services, cron, workerStreams, workerQueues, agentRunner, supertokens, redisVolatile, redisDurable or store.seaweedfs. strategy applies to Deployments only, so it is not rendered on redisDurable or store.seaweedfs, which are StatefulSets. terminationGracePeriodSeconds and lifecycle apply to all of them, and both also work on alembic.

The migration Job carries no budget and no spread constraints.

cron, workerStreams and workerQueues have no liveness probe by default. Each runs its process as PID 1, so the kubelet already restarts the container when the process exits. To detect a process that is alive but no longer working, supply your own probe with <workload>.livenessProbe.

PostgreSQL (Bundled)​

The chart includes Bitnami PostgreSQL as a subchart. It is enabled by default and creates three databases. The names depend on agenta.license.

  • OSS: agenta_oss_core, agenta_oss_tracing, agenta_oss_supertokens
  • EE: agenta_ee_core, agenta_ee_tracing, agenta_ee_supertokens
caution

The bundled PostgreSQL init scripts run only when the database volume is first created. If you change agenta.license later, the chart will not rename or recreate the databases automatically. For that case, create the new databases yourself or do a fresh install with a new PostgreSQL volume.

ValuePurposeDefault
postgresql.enabledEnable bundled PostgreSQLtrue
postgresql.auth.usernameDatabase useragenta
postgresql.primary.persistence.sizePVC size10Gi

Set the password only once, under postgres.password. The chart writes it to a Secret named <release>-pgauth and points the Bitnami subchart at that Secret. postgresql.auth.password is therefore not read, and a value set there has no effect.

A misspelled postgresql key is accepted

Nothing constrains the keys under postgresql, because the block passes values through to the subchart. helm install accepts a misspelled key and the chart falls back to the default. Render the chart and read POSTGRES_URI_CORE back before you install.

Redis​

The chart deploys two Redis instances: volatile (caching/pub-sub) and durable (queues/persistent state).

ValuePurposeDefault
redisVolatile.enabledEnable volatile Redistrue
redisVolatile.maxmemoryMax memory384mb
redisVolatile.maxmemoryPolicyEviction policyvolatile-lru
redisVolatile.passwordPassword (recommended for production)""
redisVolatile.safeToEvictLet a cluster autoscaler consolidate this pod's nodefalse
redisDurable.enabledEnable durable Redistrue
redisDurable.maxmemoryMax memory384mb
redisDurable.maxmemoryPolicyEviction policynoeviction
redisDurable.passwordPassword (recommended for production)""
redisDurable.persistence.sizePVC size5Gi
redisDurable.safeToEvictLet a cluster autoscaler consolidate this pod's nodefalse
redisVolatile.startupProbe.failureThresholdStartup probe attempts, five seconds apart, before Redis restarts60
redisDurable.startupProbe.failureThresholdStartup probe attempts, five seconds apart, before Redis restarts60

The durable instance replays its append-only file before it answers ping, and the startup probe fails until it does. The default allows about five minutes. If a replay takes longer, Redis restarts and starts the replay again. Measure the recovery time with your CPU and storage limits, then raise the threshold for that instance. For example, about 30 minutes:

redisDurable:
startupProbe:
failureThreshold: 360

Each instance runs one pod and holds state, so losing the pod takes that component down. Both carry cluster-autoscaler.kubernetes.io/safe-to-evict: "false" for that reason. supertokens carries it too.

caution

By default both Redis instances run without authentication. In shared or multi-tenant clusters, set passwords for both instances or use Kubernetes NetworkPolicies to restrict access to the Agenta namespace.

Alembic (Database Migrations)​

Migrations run as a Kubernetes Job with post-install,post-upgrade hooks. Post-hooks are used because PostgreSQL is deployed as a Bitnami subchart and is not available until after the main release installs.

ValuePurposeDefault
alembic.enabledEnable migration jobtrue
alembic.hookPhasepost or prepost
alembic.activeDeadlineSecondsJob timeout600
alembic.backoffLimitRetry count3
alembic.ttlSecondsAfterFinishedCleanup delay300

Set alembic.hookPhase: pre to run the migrations before the application pods start. Use that only with an external database (postgresql.enabled: false). With the bundled PostgreSQL, keep the default post. The StatefulSet does not exist yet at pre-install time, so the Job would wait for a database that nothing is starting. A value other than post or pre fails the render.

On the very first install in the pre phase, the Job names no ServiceAccount. It runs before the release's own ServiceAccount is applied, and Kubernetes refuses a pod that names a ServiceAccount which does not exist. The migration reaches PostgreSQL only, so on that one install its pod takes the namespace's default ServiceAccount and mounts no token. Every later upgrade names the ServiceAccount again. If a cluster policy requires a named ServiceAccount, create one yourself and set serviceAccount.create: false with serviceAccount.name.

Set Up Ingress and TLS​

Use this section when you want a public hostname instead of port-forwarding.

The chart creates one Ingress resource with four path rules on ingress.host:

  • /api routes to the API service
  • /services routes to the services backend
  • /m routes to the mobile web frontend, always
  • / routes to the web frontend

Ingress Values​

ValuePurposeDefault
ingress.enabledEnable Ingresstrue
ingress.classNameIngress class. An explicit "" omits spec.ingressClassNametraefik
ingress.hostHostnameagenta.local
ingress.tlsTLS configuration, in the shape the Ingress spec uses[]
ingress.annotationsIngress annotations{}
ingress.paths.api.pathAPI path pattern/api
ingress.paths.api.pathTypeAPI path typePrefix
ingress.paths.services.pathServices path pattern/services
ingress.paths.services.pathTypeServices path typePrefix
ingress.paths.webMobile.pathMobile web path pattern/m
ingress.paths.webMobile.pathTypeMobile web path typePrefix
ingress.paths.web.pathWeb path pattern/
ingress.paths.web.pathTypeWeb path typePrefix
ingress.extraPathsMore paths on the same host, each with path, pathType, serviceName, servicePort[]
ingress.extraHostsMore hostnames, each with host and its own paths list[]

Keep the mobile path at /m. The mobile image is built with that basePath.

A Service named in ingress.extraPaths or ingress.extraHosts must already exist in the namespace. The chart does not create it.

Ingress class name

The chart defaults to ingress.className: "traefik". If your cluster uses a different ingress controller, override this value to match.

You can check which ingress classes are available in your cluster with kubectl get ingressclass.

Some controllers ignore the field. GKE reacts only to the legacy kubernetes.io/ingress.class annotation, so there you set ingress.className: "" and route with the annotation. See Prepare a Kubernetes deployment for production.

Path Handling​

Both backends accept their own public prefix. The API strips a leading /api from every inbound request, and the services backend strips a leading /services. Each strips in a loop, so a double prefix also routes, and neither emits a redirect.

You therefore do not need path rewriting in your ingress controller. This is what lets the chart work behind a managed load balancer such as the GKE Ingress, which cannot rewrite a path.

An ingress controller that strips the prefix also works. After the strip the request arrives with no prefix, which both backends route as well. The two examples below are both valid configurations, not requirements.

To publish the services backend under a different prefix, change three values together. The variable alone is not enough: the Ingress still routes /services, and the derived agenta.servicesUrl still ends in /services. For example, for /svc:

agenta:
servicesUrl: "https://agenta.example.com/svc"
ingress:
paths:
services:
path: /svc
services:
env:
AGENTA_SERVICES_PATH_PREFIX: /svc

Set AGENTA_SERVICES_PATH_PREFIX to an empty string to turn the strip off.

Traefik: Use a StripPrefix Middleware via extraObjects:

ingress:
className: "traefik"
host: "agenta.example.com"
annotations:
traefik.ingress.kubernetes.io/router.middlewares: agenta-strip-prefixes@kubernetescrd

extraObjects:
- apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: strip-prefixes
namespace: "{{ .Release.Namespace }}"
spec:
stripPrefix:
prefixes:
- /api
- /services

NGINX Ingress Controller: Set the class. The default paths need no rewrite:

ingress:
className: "nginx"
host: "agenta.example.com"

Do not add nginx.ingress.kubernetes.io/rewrite-target. NGINX applies an Ingress annotation to every path on that Ingress, so a rewrite also changes requests to /m, and the mobile web app needs that prefix.

Enable TLS​

To enable TLS, provide a TLS secret and update your public URLs to use https://:

agenta:
webUrl: "https://agenta.example.com"
apiUrl: "https://agenta.example.com/api"
servicesUrl: "https://agenta.example.com/services"

ingress:
host: "agenta.example.com"
tls:
- secretName: agenta-tls
hosts:
- agenta.example.com

If you use cert-manager, add the appropriate annotation:

ingress:
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"

Use External Services​

You can disable any bundled infrastructure component and point to an external instance instead.

External PostgreSQL​

If you deploy Enterprise Edition, replace agenta_oss_* in the examples below with agenta_ee_*.

postgresql:
enabled: false
databases:
core: "agenta_oss_core"
tracing: "agenta_oss_tracing"
supertokens: "agenta_oss_supertokens"
external:
host: "your-pg-host.example.com"
port: 5432
username: "agenta"
sslmode: "require"

sslmode is appended to auto-constructed connection URIs only (as ?ssl= for asyncpg and ?sslmode= for the sync driver). It defaults to "prefer". Set it to "require" or "verify-full" for managed databases (e.g., AWS RDS, Cloud SQL). When using full URI overrides (uriCore, uriTracing, uriSupertokens), include the SSL parameter directly in the URI. In that case, sslmode is ignored.

Create the three databases and grant permissions before installing:

CREATE ROLE agenta WITH LOGIN PASSWORD 'your-password';

CREATE DATABASE agenta_oss_core OWNER agenta;
CREATE DATABASE agenta_oss_tracing OWNER agenta;
CREATE DATABASE agenta_oss_supertokens OWNER agenta;

-- Grants needed for schema migrations (CREATE, ALTER) and application queries.
-- You can replace ALL with specific privileges if your security policy requires it
-- (e.g., SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER for a narrower scope).
\c agenta_oss_core
GRANT ALL ON SCHEMA public TO agenta;

\c agenta_oss_tracing
GRANT ALL ON SCHEMA public TO agenta;

\c agenta_oss_supertokens
GRANT ALL ON SCHEMA public TO agenta;

You can also provide full URI overrides:

postgresql:
enabled: false
external:
uriCore: "postgresql+asyncpg://user:pass@host:5432/agenta_oss_core"
uriTracing: "postgresql+asyncpg://user:pass@host:5432/agenta_oss_tracing"
uriSupertokens: "postgresql://user:pass@host:5432/agenta_oss_supertokens"
warning

URI overrides contain credentials inline. Prefer using secrets.existingSecret or an external secrets operator to avoid storing passwords in values.yaml.

External Redis​

Give each instance a full URI. The chart passes the string through to REDIS_URI_VOLATILE and REDIS_URI_DURABLE unchanged, so credentials, the database number, and the scheme are all yours to set.

redisVolatile:
enabled: false
external:
uri: "redis://user:password@your-redis-host:6379/0"

redisDurable:
enabled: false
external:
uri: "rediss://user:password@your-redis-host:6379/1"

redis.uriVolatile and redis.uriDurable are accepted as alternatives to the two external.uri keys, and the single redis.uri is accepted as a fallback for both.

Only external.uri is read

redisVolatile.external and redisDurable.external also accept host, port and password, but the chart does not build a URI from them. If you set those three and leave uri unset, the render fails with redisVolatile.external.uri or redis.uriVolatile is required when redisVolatile.enabled=false. Put the host, the port and the credentials inside the URI.

A durable Redis with a private certificate authority

A rediss:// URI makes the client use TLS and verify the server certificate. The client trusts only the certificate authorities already in the container image. Some managed services, such as Google Cloud Memorystore, sign with an authority of their own, and the connection fails with CERTIFICATE_VERIFY_FAILED. A service with a publicly trusted certificate needs nothing here.

For the durable instance, put the authority in redisDurable.external.caCert. The chart mounts it at /etc/agenta/redis-durable/ca.pem on every workload that reads REDIS_URI_DURABLE. Point the URI at that file with the ssl_ca_certs parameter:

redisDurable:
enabled: false
external:
uri: "rediss://:password@your-redis-host:6378/0?ssl_ca_certs=/etc/agenta/redis-durable/ca.pem"
caCert: |
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----

With secrets.existingSecret, the chart writes no Secret. Put the PEM in your Secret under the key REDIS_DURABLE_CA_CERT, and set caCert: from-existing-secret so the chart still mounts it.

The volatile instance has no such setting. redisVolatile.external.caCert fails the render. Use a volatile Redis with a publicly trusted certificate.

External SuperTokens​

supertokens:
enabled: false
external:
uri: "http://your-supertokens-host:3567"

Add LLM Provider Keys and OAuth​

Pass LLM API keys under llm.<provider> and OAuth/OIDC credentials under identity.<provider>. The chart renders these into the Kubernetes Secret and injects them into the application pods as <PROVIDER>_API_KEY and <PROVIDER>_OAUTH_CLIENT_ID/SECRET env vars. LLM keys are used as fallback when end users don't provide their own.

llm:
openai: "sk-..."
anthropic: "sk-ant-..."

identity:
google:
clientId: "..."
clientSecret: "..."

For SAML/OIDC providers with extra fields (Apple, Azure AD, Boxy SAML, GitLab, Google Workspaces, Okta, …) see the full identity.* block in the example values files.

Upgrade​

Upgrade the bundled store to SeaweedFS 4.42 or later first

This applies if you set store.seaweedfs.image.tag yourself. The chart defaults to 4.47.

The API turns on bucket versioning on the bundled store at startup. Versioned deletes need SeaweedFS 4.42 or later. On an older build the delete marker does not hide the object, so a deleted file keeps showing up and cannot be opened.

To upgrade to a newer version:

helm upgrade agenta hosting/kubernetes/helm \
--namespace agenta \
-f hosting/kubernetes/oss/.values.oss.yaml

The Alembic migration job runs automatically as a post-upgrade hook. Check its status:

kubectl -n agenta get jobs -l app.kubernetes.io/component=alembic
kubectl -n agenta logs job/agenta-alembic

To pin to a specific version:

api:
image:
tag: "v0.86.8"
web:
image:
tag: "v0.86.8"
services:
image:
tag: "v0.86.8"

Uninstall​

helm uninstall agenta --namespace agenta
warning

This does not delete PersistentVolumeClaims. To fully remove data, delete the PVCs manually:

kubectl -n agenta delete pvc -l app.kubernetes.io/instance=agenta

Troubleshooting​

Pods not starting​

Check pod status and events:

kubectl -n agenta get pods
kubectl -n agenta describe pod <pod-name>

Common causes:

  • Missing secrets: ensure agenta.authKey, agenta.cryptKey, agenta.servicesInternalKey, and postgres.password are set
  • Image pull errors: verify image names and that imagePullSecrets are configured if using a private registry

Migration job fails​

Check migration logs:

kubectl -n agenta logs job/agenta-alembic

Common causes:

  • PostgreSQL not ready: the job includes an init container that waits for PostgreSQL, but external databases may have network issues
  • Wrong credentials: verify postgres.password matches the database password

Ingress not working​

Verify the Ingress resource:

kubectl -n agenta get ingress
kubectl -n agenta describe ingress agenta

Common causes:

  • Missing ingress controller: ensure Traefik or NGINX Ingress Controller is installed
  • Wrong ingress.className: it must match your ingress controller's class name. On GKE the field is ignored, and the Ingress gets no address until you set ingress.className: "" and add kubernetes.io/ingress.class: gce to ingress.annotations
  • A rewrite annotation on NGINX: nginx.ingress.kubernetes.io/rewrite-target applies to every path on the Ingress, including /m. Remove it. The backends accept their prefixes (see Path Handling)

The agent runner pod never becomes ready​

The runner binds 127.0.0.1 on its own. In a pod the kubelet connects over the pod IP, so a runner bound to loopback refuses every health probe and the pod stays unready.

The chart sets AGENTA_RUNNER_HOST=0.0.0.0 for you, so this only happens when something overrides it. agentRunner.env suppresses the chart's own entry rather than adding a second one, so an entry for AGENTA_RUNNER_HOST there replaces the chart's value. Read the value back:

kubectl -n agenta get deployment agenta-runner \
-o jsonpath='{.spec.template.spec.containers[0].env[?(@.name=="AGENTA_RUNNER_HOST")]}'

Set it with agentRunner.host instead.

Services can't connect to each other​

Check logs for connection errors:

kubectl -n agenta logs -l app.kubernetes.io/component=api --prefix

Common causes:

  • Public URLs incorrect: check agenta.webUrl, agenta.apiUrl, and agenta.servicesUrl
  • Redis not ready: check Redis pod status

The API URL must include /api​

The API URL (agenta.apiUrl) must end with /api. This is a current limitation. Authentication endpoints expect this prefix, and requests will fail with 404 if it is missing.

Correct:

agenta:
apiUrl: "https://agenta.example.com/api"

Incorrect:

agenta:
apiUrl: "https://api.agenta.example.com" # missing /api suffix

The services URL (agenta.servicesUrl) does not have this constraint.

All services must share the same origin​

Web, API, and services must be served from the same origin (same host and port). The API backend only allows cross-origin requests from a fixed set of origins, so placing the frontend and API on different ports or subdomains will cause the browser to block requests.

Route all three through a single public origin using path-based routing:

https://agenta.example.com -> web
https://agenta.example.com/api -> API (with prefix stripping)
https://agenta.example.com/services -> services (with prefix stripping)

You can do this with the built-in Ingress resource or with an external reverse proxy (Traefik, NGINX, Caddy, etc.).

Environment changes require a pod restart​

If you change environment variables outside of Helm (for example, by editing the deployment directly), restart the web pods for the new values to take effect:

kubectl -n agenta rollout restart deployment/<release>-agenta-oss-web

The web frontend generates its runtime configuration file (__env.js) once at pod startup. New environment values only appear after the pod restarts.

Note that helm upgrade handles this automatically by updating the deployment template, which triggers a rollout. A manual restart is only needed when you change variables without going through Helm.

Also note that any active kubectl port-forward sessions will break after a rollout because they are bound to the old pod. Restart your port-forward commands after each rollout.

Getting Help​

If you run into issues: