Gauntlet

Install self-hosted Gauntlet on Kubernetes

One Helm release. It runs on Community (free: 3 environments, 3 tests at a time) until you add a license.

On this page
  1. You need
  2. Sign-in app
  3. Values
  4. Install
  5. First admin
  6. License
  7. Email
  8. Preview links
  9. Upgrade
  10. Back up
  11. Uninstall
  12. Replicas
  13. Security model
  14. Troubleshooting
  15. Values reference

You need

ClusterKubernetes 1.28 or newer, Helm 3, a default StorageClass (or set persistence.storageClass and mongo.persistence.storageClass).
Permissions By default the platform's ServiceAccount is cluster-admin: it creates a namespace, quota and network policy per environment, on the fly. rbac.clusterAdmin: false uses a ClusterRole with only what it needs (still cluster-wide; no CRDs, webhooks, cluster RBAC or nodes). On clusters without a HelmChart API (EKS, GKE, AKS) the chart also runs the k3s helm-controller for Helm-chart apps; it is always cluster-admin.
Capacity Platform 0.1 CPU, 128–512 MiB. Bundled MongoDB 0.1 CPU, 256 MiB–1 GiB. Each environment: about 4 GiB memory for a Helm-chart app (its virtual cluster), within a per-environment quota of 6 CPU / 10 GiB requested, 16 GiB limit, 40 pods (environments.quota.*). Each running test: 0.5 CPU, 1–3 GiB.
Storage5 GiB for MongoDB, 10 GiB for screenshots and recordings (two PVCs).
Sign-inAn OpenID Connect provider: GitLab, Google, Okta, Keycloak…
ModelAn Anthropic API key, or an Anthropic-compatible AI gateway, or neither (then everyone adds their own key in the console).
Network inAn ingress or load balancer for publicUrl, with TLS (ingress.*, or your own in front of the Service gauntlet, port 8090).
Network outYour OIDC issuer; the model API or gateway; the license server (not when air-gapped); image registries (ghcr.io, docker.io, yours); https://charts.loft.sh (virtual clusters) and the chart repositories your blueprints use.
Preview linksOptional: wildcard DNS and certificate, see Preview links.

1. Register Gauntlet with your sign-in provider

GitLab: instance-wide under Admin → Applications → Add new application, or for one group under Group → Settings → Applications.

NameGauntlet
Redirect URI<publicUrl>/auth/callback, e.g. https://gauntlet.acme.com/auth/callback
TrustedOn (no consent screen; instance applications only)
ConfidentialOn
Scopesopenid email profile

Then oidc.issuer = your GitLab URL, oidc.clientId = the Application ID, client secret = the Secret. Other providers: the same redirect URI and scopes; the issuer is the URL that serves /.well-known/openid-configuration.

Limit who can sign in: oidc.allowedDomains (email domains) or oidc.allowedGroups (the provider's groups claim; on GitLab, group paths such as acme/platform).

2. Write your values

Required: publicUrl, oidc.issuer, oidc.clientId and the client secret. Keep secrets in a Secret you manage:

kubectl create namespace gauntlet
kubectl -n gauntlet create secret generic gauntlet-secrets \
  --from-literal=oidc-client-secret='<client secret>' \
  --from-literal=llm-api-key='<gateway key>' \
  --from-literal=cookie-secret="$(openssl rand -base64 32)" \
  --from-literal=secrets-key="$(openssl rand -base64 32)" \
  --from-literal=mongo-root-password="$(openssl rand -hex 24)"
# values.yaml
image: ghcr.io/gauntlet-dev/gauntlet:1.0.0
runnerImage: ghcr.io/gauntlet-dev/gauntlet-runner:1.0.0
publicUrl: https://gauntlet.acme.com
oidc:
  issuer: https://gitlab.acme.com
  clientId: <application id>
  # optional:
  allowedGroups: [acme]
# the Secret above
existingSecret: gauntlet-secrets
# installation admins (none: a one-time bootstrap token, see below)
adminEmails: [dana@acme.com]
# where licenses are renewed
licenseServer: https://usegauntlet.dev
# an AI gateway; or anthropicApiKey (Secret key anthropic-api-key)
llm:
  baseUrl: https://ai-gateway.acme.com
ingress:
  enabled: true
  className: nginx
  tls: [{ secretName: gauntlet-tls, hosts: [gauntlet.acme.com] }]
# license: paste it in the console later (or the Secret key license)
# mongo.uri: your own MongoDB instead of the bundled one (or the Secret key mongo-uri)

existingSecret names one Secret; keys you leave out fall back to the values. Keys: oidc-client-secret license anthropic-api-key claude-oauth-token llm-api-key smtp-password bootstrap-token mongo-uri cookie-secret secrets-key mongo-root-password. GitOps (Argo CD, Flux): put cookie-secret, secrets-key and mongo-root-password there. Otherwise the chart generates them with lookup, which those tools don't run, and every sync rotates them.

3. Install

# chart 1.0.0
curl -fsSLo gauntlet-chart.tgz https://usegauntlet.dev/download/gauntlet-chart.tgz
helm install gauntlet ./gauntlet-chart.tgz -n gauntlet -f values.yaml --wait
# checks the console, MongoDB and OIDC discovery
helm test gauntlet -n gauntlet
# the version you run
curl -fsS https://gauntlet.acme.com/api/version

The install output repeats the callback URL to register and how the first admin signs in. Open publicUrl and sign in.

4. First admin

  • adminEmails set: those people are installation admins (license, users) when they sign in.
  • Not set: the server prints a one-time bootstrap token in its log. Whoever signs in with it becomes the first admin; once there is an admin it never works again.
    kubectl -n gauntlet logs deploy/gauntlet | grep -i bootstrap
    # then open https://gauntlet.acme.com/auth/login?bootstrap=<token>
    To choose it yourself: bootstrapToken, or the Secret key bootstrap-token.

5. License

Buy one on the pricing page; it's emailed and kept in your license account. Install it on the console's License page, <your Gauntlet URL>/settings/license (paste; takes effect at once), or as the Helm value license / Secret key license. A license installed in the console wins over the Helm one.

ValidityEach license is valid for about 30 days (Valid until on the License page). The installation fetches a fresh one from the license server automatically: normally nothing to do.
Air-gappedSet licenseRefresh: "off" (no calls out) and paste a fresh license from your license account before Valid until.
Installation IDOn the License page. Give it when you buy, or later in the license account; a license without one binds to the first installation that renews it. Stored in MongoDB: a reinstall that keeps the data keeps the ID.
Move itLicense account → Use on another installation. The old installation gets no more renewals and drops to Community when its current license runs out.
Subscription ends14 days of grace, then Community. Nothing is deleted.

Terms: license terms, terms of service, privacy.

6. Email (optional)

smtp:
  host: smtp.acme.com
  port: 587
  username: gauntlet
  from: "Gauntlet <gauntlet@acme.com>"
  # starttls | tls | none
  tls: starttls
# password: the Secret key smtp-password (or smtp.password)

For invites and license warnings. Without smtp.host no email is sent: the console shows the invite link to copy and says so.

7. Preview links (optional)

Each environment's services get links like https://web--<env>.preview.acme.com. Set previewDomain: preview.acme.com, point wildcard DNS *.preview.acme.com at the ingress, and add a wildcard certificate to ingress.tls for https. With ingress.enabled the chart routes *.previewDomain too.

Upgrade

helm upgrade gauntlet ./gauntlet-chart-<version>.tgz -n gauntlet -f values.yaml --wait
curl -fsS https://gauntlet.acme.com/api/version
  • Always pass your values: without -f values.yaml Helm stops on the missing required values.
  • One replica, replaced in place: the console is down during the restart. Data and running environments are kept.
  • Back up first (below). Then everyone updates their CLI: curl -fsSL https://gauntlet.acme.com/api/cli/install.sh | sh. gauntlet version shows CLI and server.

Back up and restore

MongoDBEverything: users, projects, runs, findings, stored model keys (encrypted), license, installation ID.
PVC gauntlet-dataScreenshots, recordings, logs.
Secret gauntlet (or your existingSecret)secrets-key decrypts stored model keys: without it they must be added again. cookie-secret keeps people signed in.
# bundled MongoDB
kubectl -n gauntlet exec deploy/gauntlet-mongo -- sh -c \
  'mongodump --archive --gzip -u root -p "$MONGO_INITDB_ROOT_PASSWORD" --authenticationDatabase admin' > gauntlet-mongo.gz
# artifacts and secrets
kubectl -n gauntlet exec deploy/gauntlet -- tar czf - -C /data . > gauntlet-data.tgz
kubectl -n gauntlet get secret gauntlet -o yaml > gauntlet-secret.yaml

Restore: install with the same secrets (existingSecret, or kubectl apply -f gauntlet-secret.yaml first), then:

kubectl -n gauntlet scale deploy/gauntlet --replicas=0
kubectl -n gauntlet exec -i deploy/gauntlet-mongo -- sh -c \
  'mongorestore --archive --gzip --drop -u root -p "$MONGO_INITDB_ROOT_PASSWORD" --authenticationDatabase admin' < gauntlet-mongo.gz
kubectl -n gauntlet scale deploy/gauntlet --replicas=1
kubectl -n gauntlet exec -i deploy/gauntlet -- tar xzf - -C /data < gauntlet-data.tgz

External MongoDB (mongo.uri): use your database's own backups.

Uninstall

helm uninstall gauntlet -n gauntlet

Kept on purpose, so a reinstall picks up where you left off: the PVCs gauntlet-mongo and gauntlet-data, the Secret gauntlet and the Secret gauntlet-installation (your installation ID, so licenses keep working after a reinstall; delete it to get a new ID). Also left behind: running environments (namespaces labelled app.kubernetes.io/managed-by=gauntlet; a reinstall on the kept data manages them again, and removes those it no longer knows after 10 minutes), the namespace gauntlet-system and the helm.cattle.io CRDs. To remove everything:

# environments (better: stop them in the console first)
kubectl delete ns -l app.kubernetes.io/managed-by=gauntlet
kubectl -n gauntlet delete pvc gauntlet-mongo gauntlet-data
# also deletes the Secrets gauntlet and gauntlet-installation
kubectl delete ns gauntlet-system gauntlet
# only if nothing else uses them (k3s and RKE2 ship them built in):
kubectl delete crd helmcharts.helm.cattle.io helmchartconfigs.helm.cattle.io

Replicas

Gauntlet runs as one replica (Recreate updates). The reconciler holds a Kubernetes Lease (leader election), so a second pod started by mistake doesn't manage environments twice. Availability comes from quick restarts and backups; for a replicated database use your own MongoDB (mongo.uri).

Security model

WhatWhere and how
Test environmentsA new namespace env-<id> per environment, on your cluster's nodes, running the app built from the project's branch.
Agent runnerThe AI agent and its browser, in the environment's namespace. Its kubeconfig is admin of that namespace only (or its vcluster) and expires with the run. Model calls go through the platform's proxy with a run-scoped token: your model key never enters the environment.
RuntimeThe cluster default (usually runc, a shared kernel). For untrusted code set runtimeClass to a gVisor or Kata RuntimeClass; it applies to the runner and container apps, not to Helm-chart apps (their pods run inside a virtual cluster on the default runtime). Give that RuntimeClass scheduling.nodeSelector to keep them on dedicated nodes.
NetworkEach environment gets a NetworkPolicy: in only from itself and the platform; out only to DNS, itself, the platform API and the internet. Private ranges and cloud metadata are blocked, so environments can't reach MongoDB, other namespaces or each other. Your CNI must enforce NetworkPolicies.
LimitsPod Security baseline on environment namespaces; a ResourceQuota (environments.quota.*) with no LoadBalancer or NodePort Services.
Platform RBACCluster-wide, to create and delete environment namespaces and their contents. cluster-admin by default; rbac.clusterAdmin: false uses a narrower ClusterRole (listed in the chart README). The optional helm-controller is always cluster-admin.

Troubleshooting

kubectl -n gauntlet get pods
kubectl -n gauntlet logs deploy/gauntlet --tail=100
helm test gauntlet -n gauntlet
SymptomCheck
Pod not ready (/readyz fails)The log says what is down: MongoDB or OIDC discovery. /livez stays up, so an outage doesn't restart the pod.
Sign-in failsRedirect URI exactly <publicUrl>/auth/callback; client ID and secret; the issuer's /.well-known/openid-configuration reachable from the cluster; oidc.allowedDomains / allowedGroups.
Signed out again right awayThe session cookie is Secure when publicUrl is https: TLS must terminate in front of Gauntlet.
Changed a secret, nothing happenedValues: helm upgrade … -f values.yaml restarts the pod. A Secret you manage: kubectl -n gauntlet rollout restart deploy/gauntlet.
Runs fail: AI gateway unreachablellm.baseUrl from inside the cluster, the key llm-api-key, egress policies.
Environments wait for roomCluster capacity vs environments.quota.*, or the plan's environment limit (/settings/license).
Preview links don't openpreviewDomain, wildcard DNS and certificate.
MonitoringPrometheus metrics at /metrics (set GAUNTLET_METRICS_TOKEN under extraEnv to require a bearer token); probes /livez and /readyz; version at /api/version.

Values reference

The chart's README lists every value, the RBAC rules and what uninstall leaves: helm show readme ./gauntlet-chart.tgz. Defaults and comments: helm show values ./gauntlet-chart.tgz.