Prerequisites
- Kubernetes 1.28+
- Helm 3.12+
kubectlconfigured for your cluster- A StorageClass that supports dynamic provisioning (for persistent volumes)
- A domain name (for Ingress with TLS)
- Default resource requirements: ~5 CPU and ~17 Gi RAM (requests). See Size Overlays for smaller or larger configurations.
Quick Start
Deploy LangWatch with all dependencies managed by the chart:Operator access and feature flags
The Ops pages (/ops) are open only to the operators listed in ADMIN_EMAILS. No account has them on a new install. Add your email and upgrade:
/ops/feature-flags), for all projects or for chosen ones. Dashboard widgets need both release_dashboards and release_lwql_workbench. The Query API (POST /api/v1/query) works without either.
License
An Enterprise license goes inapp.license.key, inline or from a Secret. It takes either an activation code (LW-XXXX-XXXX-XXXX-XXXX), which needs outbound HTTPS to connect.langwatch.ai, or a signed license key, which works offline:
Low-Resources Deployment
The default install requests ~5 CPU and ~17 Gi RAM. For smaller clusters or evaluation purposes, use the dev overlay which requests approximately ~2.8 CPU and ~6.9 Gi RAM:Production Deployment
For production, you should:- Use external managed databases (PostgreSQL, Redis)
- Provide Kubernetes Secrets from your secret manager
- Expose via Ingress with TLS
- Disable auto-generation
1. Provide the Secrets
Withautogen.enabled: false the chart creates no Secrets; it reads them from Secrets you provide. Source them from your secret manager (External Secrets Operator, Sealed Secrets, Vault, Azure Key Vault, …) so values stay stable across syncs and can be rotated. Every value is an opaque random string, e.g. openssl rand -hex 32. Which keys you need depends on what you run:
App Secret: referenced by secrets.existingSecret (default name langwatch-app-secrets). The app, workers, gateway and Langy pods all read it.
Every key name can be changed to match your secret store, for example the dashed names Azure Key Vault produces through
dataFrom: secrets.secretKeys.* for the app and LangWatchQL keys, gateway.secrets.internalSecretKey and gateway.secrets.jwtSecretKey for the gateway keys (the app and the gateway pod both read them), and langyagent.secrets.internalSecretKey for Langy.
ClickHouse Secret: chart-managed ClickHouse only (skip it with clickhouse.chartManaged: false). Referenced by clickhouse.auth.existingSecret, and the name must differ from the default <release>-clickhouse, since the deployment composes CLICKHOUSE_URL from the password key at runtime.
Redis and PostgreSQL Secrets: chart-managed Redis or PostgreSQL only. Set
redis.auth.existingSecret and postgresql.auth.existingSecret to Secrets holding their passwords, with the key names in redis.auth.secretKeys.passwordKey and postgresql.auth.secretKeys.*. With autogen.enabled=false the render fails without them (or an inline redis.auth.password / postgresql.auth.password), because a generated password would change on every Argo CD sync.
A missing required key leaves the pods that read it in CreateContainerConfigError, naming the key. When the Secret already exists without LANGY_INTERNAL_SECRET, helm install and helm upgrade stop at render time and name the key instead. Argo CD renders without cluster access, so under Argo CD only the pod error shows it.
autogen.enabled controls the app Secret (app, gateway and Langy keys), the LangWatchQL passwords and the chart-managed ClickHouse, PostgreSQL and Redis credentials. Use it with plain helm only: it reads existing values back from the cluster, which Argo CD cannot do, so under Argo CD the generated values change on every sync. Each database’s chartManaged setting decides whether the chart runs that database: to bring your own PostgreSQL, Redis or ClickHouse, set its chartManaged: false.LangWatchQL is optional. The two
LWQL_* values are passwords for dedicated read-only identities the app creates for user-written queries: a PostgreSQL role lwql_ro (read-only transactions, a statement timeout, SELECT on approved views only) and a matching ClickHouse identity. The app keeps writing through its own connection. Without these keys every pod still starts and LangWatchQL stays unavailable; set lwql.enabled: false to turn it off entirely.To add the keys later on chart-managed ClickHouse, delete the <release>-lwql-access-render-<hash> Job so the next sync or upgrade runs it again. Once it is Complete, restart the ClickHouse StatefulSet and the app and workers Deployments, which read the passwords at startup.With external PostgreSQL, the DATABASE_URL user needs CREATE ROLE, or at least ALTER ROLE on a pre-created lwql_ro, because the app re-converges the role on every boot. Without it LangWatchQL stays unavailable and the rest of LangWatch runs normally. See the chart README for the exact role statements.Clusters that forbid root pods (Kyverno/Gatekeeper This removes the pod and every reference to it, and
runAsNonRoot policies): the Langy assistant pod runs as UID 0 with a small set of added capabilities, which it needs to isolate each session under its own UID. If your policy blocks that, turn Langy off. Every other LangWatch pod already runs as non-root:LANGY_INTERNAL_SECRET is no longer needed. A sandboxed runtime (see Langy) hardens Langy where root pods are allowed; it does not satisfy a runAsNonRoot policy. For Pod Security Admission restricted, which also rejects the bundled Prometheus, apply the strict-admission overlay instead.2. Create a Values File
Start from the production example and customize. This configuration requests approximately ~8.4 CPU and ~32 Gi RAM across all pods:3. Install
4. Verify
High-Availability Deployment
For HA with replicated ClickHouse, multiple app/worker replicas, and PodDisruptionBudgets. This configuration requests approximately ~31 CPU and ~88 Gi RAM across all pods:Replicated ClickHouse requires an odd number of replicas (3, 5, 7) for Keeper consensus. Use 3
replicas unless you have a specific reason for more.
Overlay System
The chart ships with composable overlay files inexamples/overlays/. Combine them to build your deployment configuration:
Size Overlays
size-prod.yaml and size-ha.yaml run more than one replica, so they need app.dataplane (S3 or Azure Blob) for stored objects. cold-storage-s3.yaml covers ClickHouse only.
Access Overlays
Infrastructure Overlays
Composing Overlays
Overlays are composable, later files override earlier ones:ClickHouse Configuration
Standalone vs Replicated
Switch to replicated mode:
External ClickHouse
To use an existing ClickHouse instance:Auto-Tuning
Theclickhouse-serverless subchart automatically tunes ClickHouse parameters based on the CPU and memory you allocate:
AI Gateway sub-chart
The umbrella chart bundles the AI Gateway as a sub-chart that runs alongside the core LangWatch app, and installs it by default. It gives you virtual keys, hierarchical budgets, multi-provider routing via Bifrost, guardrails, and prompt caching, all governed by the same control plane. To run without it:ingress.gateway.host on the app Ingress, or route to the <release>-gateway Service yourself and set gateway.publicUrl. Routing and ingress controllers lists the paths, timeouts and body sizes for ingress-nginx, Envoy Gateway and other controllers. For per-environment tuning (replicas, autoscaling, ingress hostname + TLS, image registry mirror, secrets injection) see AI Gateway → Self-hosting → Helm.
Three things to know when running it:
-
One Secret holds everything (or use
autogen.enabled=trueand let the chart materialise it). Bothlangwatch-appand the AI Gateway pod mountLW_GATEWAY_INTERNAL_SECRETandLW_GATEWAY_JWT_SECRETfrom the samesecrets.existingSecretSecret (defaultlangwatch-app-secrets), which also holds the app keyscredentialsEncryptionKey,nextAuthSecretandvirtualKeyPepper. The Production Deployment section above lists the keys the Secret needs. Withautogen.enabled=falseand a required key missing, the pods that read it stay inCreateContainerConfigErrornaming the key (under plainhelm, a missingLANGY_INTERNAL_SECRETin an existing Secret stops the render instead); underautogen.enabled=truethe chart materialises the Secret on first install via lookup-or-rand. -
A public route needs DNS + TLS. The gateway is what your LLM clients hit, so it usually wants its own hostname (e.g.
gateway.your-corp.com), separate cert, separate route. See AI Gateway → Self-hosting → DNS & TLS. -
Worker pods must be running. Budget enforcement reads from a ClickHouse rollup that trace-processing folds into. The workers also run the upgrade, so the chart refuses
workers.enabled=false.
Upgrade
pnpm task upgrade on start, and the new api pods are not ready until it completes, so the previous version keeps serving meanwhile and if it fails. When upgrades are serialised (serializeUpgrades on and no app.dataplane), a pre-upgrade Job runs it on the new image before any pod rolls instead, and a failure fails the helm upgrade. On a first install the worker runs it once on its first boot.
Follow it on Ops > Upgrades or with pnpm task upgrade status. With serialised upgrades, to run the upgrade from your own pipeline instead, set app.migrations.preRoll: false and run it before helm upgrade; until then the new api pods are not ready.
See Upgrade Guide for version-specific instructions.
Uninstall
FAQ
Istio, Service Mesh
If you’re using Istio or another service mesh with automatic sidecar injection, Job pods may never finish because the sidecar keeps the pod alive after the main container exits. The chart ships no CronJobs of its own (cronjobs.jobs is empty by default). If you add jobs there, disable sidecar injection for them:
clickhouse.backup.enabled is on) and the chart’s own setup Jobs (the LangWatchQL render and the stored-objects upgrade hooks) take no pod annotations, so the setting above does not reach them. For those, enable native sidecars (Kubernetes 1.29 or later, Istio ENABLE_NATIVE_SIDECARS=true), which stop when the Job’s container exits, or exclude the pods in your mesh’s injection configuration.
Two installs in one cluster
The bundled Prometheus creates a cluster-scoped ClusterRole named<release>-prometheus-server. A second install with the same release name in another namespace, for example a staging langwatch beside a production langwatch, fails with ClusterRole "langwatch-prometheus-server" ... exists and cannot be imported into the current release. Give the second install its own ClusterRole name, or use a different release name:
ingress.hosts and ingress.gateway.host. Overlapping host and path rules conflict in the ingress controller.
Custom StorageClass
Set a StorageClass for all persistent volumes:Air-Gapped Environments
For clusters without internet access:- Push LangWatch images to your private registry
- Update
images.app.repository,images.langwatch_nlp.repository,images.langevals.repository, plus the bundled services listed in Docker Images - If your registry requires authentication, attach the pull secret to the ServiceAccount the pods use. The chart has no chart-wide
imagePullSecretsvalue; see Private Registries - License with a signed license key in
app.license.key, not an activation code: redeeming a code needs outbound access toconnect.langwatch.ai