Install with Helm
Install the private release into a validated Kubernetes cluster, choose a storage backend, and enable the modules your deployment requires.
Before you begin#
- Kubernetes 1.27 or later, with cluster-admin rights to install CustomResourceDefinitions.
- Helm 3.
- The versioned release bundle and image-registry access supplied during onboarding.
Verify and extract the versioned chart before using a profile:
sha256sum --check SHA256SUMS
tar -xzf agentfleet-<version>.tgz
Install the CustomResourceDefinitions#
CRDs install separately from the chart so that upgrading the control plane never rewrites your API types by surprise.
kubectl apply -f crds/
kubectl get crd | grep agentfleet.io
Install the chart#
Start from a profile rather than raw defaults. A profile is an opinionated set of values for a situation; anything in it can still be overridden.
helm install agentfleet ./agentfleet \
--namespace agentfleet-system --create-namespace \
-f agentfleet/profiles/enterprise-baseline.yaml \
-f agentfleet-images.values.json \
--set gateway.config.auth.mode=entra_app \
--set gateway.config.auth.entraApp.directoryID=<directory-id> \
--set gateway.config.auth.entraApp.requiredRole=<app-role> \
--set gateway.config.auth.oidcJwt.issuer=https://login.microsoftonline.com/<directory-id>/v2.0 \
--set 'gateway.config.auth.oidcJwt.audience[0]=<token-audience>' \
--set gateway.config.auth.oidcJwt.jwksURL=https://login.microsoftonline.com/<directory-id>/discovery/v2.0/keys \
--set gateway.config.auth.entraApp.bindings.existingSecret=<identity-db-secret> \
--set gateway.config.auth.entraApp.auditDigest.existingSecret=<audit-digest-secret>
helm install agentfleet ./agentfleet \
--namespace agentfleet-system --create-namespace \
--set gateway.config.auth.allowUnregisteredCallers=true
helm template agentfleet ./agentfleet \
--namespace agentfleet-system \
-f agentfleet/profiles/enterprise-baseline.yaml \
-f agentfleet-images.values.json \
--set gateway.config.auth.mode=entra_app \
--set gateway.config.auth.entraApp.directoryID=<directory-id> \
--set gateway.config.auth.entraApp.requiredRole=<app-role> \
--set gateway.config.auth.oidcJwt.issuer=https://login.microsoftonline.com/<directory-id>/v2.0 \
--set 'gateway.config.auth.oidcJwt.audience[0]=<token-audience>' \
--set gateway.config.auth.oidcJwt.jwksURL=https://login.microsoftonline.com/<directory-id>/discovery/v2.0/keys \
--set gateway.config.auth.entraApp.bindings.existingSecret=<identity-db-secret> \
--set gateway.config.auth.entraApp.auditDigest.existingSecret=<audit-digest-secret>
A newly applied agent does not route on these commands. An applied agent is pending in the Registry and does not route until it is approved (the approval routes of issue #727 are only a first slice in the Registry: off by default, not enabled by the chart, and not called by the Console or the command line) or, on a profile that sets registry.operatorWritesAreApprovals=true, until an operator write is taken as the approval. That setting records no person’s decision, and with Registry write authentication off, the default, any caller that can reach the write API can make a record routable. local-light, local-durable, demo-observability and demo-registration set it when their file is passed (not for an older release upgraded with --reuse-values: see Install with Helm); the chart default, enterprise-baseline, enterprise-postgres and secure-production do not. On PostgreSQL storage, turning it on later affects agents created afterwards, not one that is already pending; on memory storage the restart that turning it on causes loses the Registry’s records, and the Operator’s next republish creates each agent again as registered (read from the chart and the Registry code; not run on a cluster).
The Gateway's default auth mode is off. off, header_dev and oidc_jwt admit a caller with no Registry caller record, so the caller is not checked against the Registry; entra_app requires an approved Registry binding for each caller and takes the tenant from the Registry record. off and header_dev are for local development only. oidc_jwt may run in production: it verifies the token's signature, issuer, audience and time claims and takes the tenant and actor from the verified claims, but it does not check that the caller or principal is registered with the Registry. The chart refuses to render the Gateway in off, header_dev or oidc_jwt unless gateway.config.auth.allowUnregisteredCallers=true acknowledges that; entra_app needs no acknowledgement. The profile and dry run commands above choose entra_app and suit callers that are Entra applications; the defaults command is a local evaluation, so it carries the acknowledgement. Replace each <placeholder> with your own value: your Entra directory ID, the application role callers must hold, the audience of the tokens, and the names of two Secrets you create first (the identity-binding database DSN and the audit-digest key); the values reference lists every gateway.config.auth.entraApp key. The local-light, local-durable and demo-observability profiles set the acknowledgement themselves.
For callers that use another identity provider, replace the entra_app flags in the profile or dry run command with these:
--set gateway.config.auth.mode=oidc_jwt \
--set gateway.config.auth.oidcJwt.issuer=https://<your-idp> \
--set 'gateway.config.auth.oidcJwt.audience[0]=<token-audience>' \
--set gateway.config.auth.oidcJwt.jwksURL=https://<your-idp>/.well-known/jwks.json \
--set gateway.config.auth.allowUnregisteredCallers=true
An existing install in off, header_dev or oidc_jwt must add the acknowledgement, or move to entra_app, before upgrading, or the upgrade fails to render. If a command is refused, see Troubleshooting.
Profiles that enforce policy ship no allow rules, so the control plane denies every request until you author them. That is deliberate — a chart must not ship someone else's authorization policy. See Write a policy that allows traffic.
Choose a storage backend#
Registry, approval, context, and Console sessions each keep state. In-memory storage is the default and is fine for evaluation; it loses everything on restart and cannot run more than one replica.
For anything durable, point each module at PostgreSQL using a Secret that holds its DSN:
kubectl create secret generic agentfleet-registry-postgres \
--namespace agentfleet-system \
--from-literal=dsn='postgres://user:password@host:5432/agentfleet?sslmode=require'
helm upgrade agentfleet ./agentfleet \
--namespace agentfleet-system --reuse-values \
--set registry.storage.type=postgres \
--set registry.storage.postgres.existingSecret=agentfleet-registry-postgres
The chart runs a migration Job as a pre-install and pre-upgrade hook. Migrations
are transactional, take an advisory lock, and are checksummed, so a partially applied or edited migration
history fails loudly instead of drifting.
Every storage key is listed in the generated Helm values reference.
Upgrade#
helm upgrade agentfleet ./agentfleet \
--namespace agentfleet-system --reuse-values
This keeps the release's own values. A release that runs the Gateway in entra_app needs no new flag to render on upgrade, and neither does one installed with gateway.config.auth.allowUnregisteredCallers=true (as the defaults command above does), because the value is kept. A release in off, header_dev or oidc_jwt that was installed without it, for example by an older chart, is refused until the command adds --set gateway.config.auth.allowUnregisteredCallers=true, which keeps the release exactly as it was, or the entra_app flags shown above (create the Secrets they name first).
--reuse-values: what stops routing depends on the storage
--reuse-values with no -f reuses the values the release was installed with, which lack registry.operatorWritesAreApprovals, so the upgrade renders no -operator-writes-are-approvals=true even for a release installed from local-light, local-durable, demo-observability or demo-registration (make install-profile passes -f and is not affected). On memory storage (the chart default, enterprise-baseline, local-light) the Registry restarts with no records, the Operator republishes every agent as pending, and no agent routes until a person decides (issue #727, not built yet) or the setting is on. On PostgreSQL (local-durable, enterprise-postgres, secure-production) existing registered and managed agents keep routing; only agents applied after the upgrade are pending. In both cases, if the release depends on operator writes approving, add --set registry.operatorWritesAreApprovals=true or re-pass the profile with -f. The chart defaults are not changed and helm upgrade does not warn you; the owner accepted this on 2026-11-11. To restore routing (read from Helm’s --reuse-values semantics and the template; helm template on a profile’s old values renders no such flag; not run on a cluster):
- List the affected agents:
GET /v1/agents?trustLevel=pendingon the Registry (with the read token where read auth is on). - Turn the setting on and keep the release’s other values:
On memory storage the Registry restarts and loses its records again, and the Operator’s next republish (every 5 minutes by default) writes each agent as an operator write, which the setting takes as the approval, so each ishelm upgrade agentfleet ./agentfleet \ --namespace agentfleet-system --reuse-values \ --set registry.operatorWritesAreApprovals=trueregistered. Put the same--seton the upgrade itself to avoid the gap, or re-pass the profile with-f. - Check that the list from step 1 is empty, then send a request to an agent.
This records no person’s decision: with registry.security.auth.mode: token (enterprise-baseline) the holder of the write token is the approver, with it off any caller that can reach the write API is. If that is not acceptable, leave the setting off and the agents stay pending until a decision is recorded (the approval routes of issue #727 are only a first slice in the Registry, not enabled by the chart).
Apply any new CRDs first — Helm does not upgrade CRDs installed outside the chart:
kubectl apply -f crds/
Uninstall#
helm uninstall agentfleet --namespace agentfleet-system
CRDs and their resources are left in place so that uninstalling the control plane never deletes your agent definitions. Remove them explicitly when you mean to:
kubectl delete -f crds/
Next#
- Verify your install — confirm health before sending traffic.
- Profiles — what each shipped profile turns on.
- Helm values reference — every key the chart accepts.