- Docker Compose: Set in your
.envfile - Helm chart: Set via
values.yaml(the chart maps values to env vars automatically) - Raw Kubernetes: Set directly in your Deployment manifests
Core Configuration
There is no in-process worker mode. The app and the background workers are always separate
processes/pods. See Architecture and the
workers.*
Helm values below.Secrets
The AI Gateway uses three more secrets,
LW_GATEWAY_INTERNAL_SECRET, LW_GATEWAY_JWT_SECRET and LW_VIRTUAL_KEY_PEPPER. Set all three or none. See AI Gateway security.
License
Authentication
A single sign-on connection is set up per organization in Settings > Authentication, with no environment variables. See SSO Configuration. Installs that configure a deployment-wide provider with
AUTH_PROVIDER and its provider variables keep working; see Upgrading SSO.
Restricting sign-up
By default anybody who can reach the install can create an account and then create an organization of their own. To close that:SIGN_UP_MODE=invite_onlyallows an account only for an address with a pending invitation. Members join the organizations that invited them. Only the operators inADMIN_EMAILScan create a new organization, apart from the first organization on an install that has none.SIGN_UP_ALLOWED_DOMAINSallows an account only for the listed email domains. It works with either mode.
ADMIN_EMAILS can always sign up.
The first administrator on an invite-only install:
- Set
ADMIN_EMAILSto your address, then sign up with it. No other address can sign up without an invitation. - Create the first organization and invite the rest of your team from Settings > Members.
ADMIN_EMAILS empty, the first account on an install with no users is still allowed, so the install can be set up. When SIGN_UP_ALLOWED_DOMAINS is set, that first account also has to be on a listed domain. Anybody who reaches the install before you can take that account, so set ADMIN_EMAILS on an install that is reachable from outside your network.
The restriction applies to email and password sign-up, passkey sign-up, and first sign-in through a deployment-wide identity provider (AUTH_PROVIDER). It does not apply to an organization’s own single sign-on connection or to SCIM provisioning: those are the organization’s decision. Existing accounts are not affected.
Network and Access
Services
LANGWATCH_NLP_INTERNAL_SECRET is optional, and how you provide it depends on the deployment:
- Docker Compose: the app, the workers and
langwatch_nlpall read.env, so one line covers every container. Generate a value withopenssl rand -hex 32. The NLP service is not published on a host port, so only the compose network reaches it. - Helm chart: the chart generates the value into the app Secret and points the app, the workers and the NLP Deployment at the same key, so a default install needs no change. If you supply the app Secret yourself, add a
LANGWATCH_NLP_INTERNAL_SECRETkey to it (or rename the key withlangwatch_nlp.secrets.internalSecretKey) and roll the three Deployments.
Object Storage (S3)
The dataplane S3 bucket is the general file-storage layer for all externalized byte content. Current consumers:- Stored objects: externalized byte content (audio, image, video, document) extracted from incoming events and dataset uploads. Bytes are content-addressed under
{projectId}/{sha256}and served back viaGET /api/v1/files/:id. - Dataset uploads: persists rows uploaded through the dataset UI. Shares the same
S3_BUCKET_NAMEbucket.
S3_BUCKET_NAME is set, all consumers use that bucket. When it is not set, stored-objects fall back to the local filesystem at LANGWATCH_LOCAL_STORAGE_PATH. That is fine for single-replica installs, not for horizontally-scaled deployments (see warning below).
When running on AWS with IRSA (IAM Roles for Service Accounts), you don’t need to set S3 access
keys. The pod’s service account assumes the IAM role automatically.
Object Storage (Azure Blob)
Azure Blob Storage is an alternative to S3 for the same dataplane consumers (stored objects + dataset uploads). It is selected only by the explicitSTORED_OBJECTS_BACKEND=azure toggle. Setting the AZURE_BLOB_* variables below by themselves has no effect. This is deliberate (issue #4133): deployment behavior must not depend on which env vars happen to be set, so a stray AZURE_BLOB_ACCOUNT_KEY left over from a decommissioned integration can never silently redirect writes.
Precedence, evaluated in this order: a per-project BYOC S3 bucket (if configured for that project) beats the azure toggle; the azure toggle beats S3_BUCKET_NAME; S3_BUCKET_NAME beats the local-filesystem fallback. This azure backend covers datasets too: the destination resolver is shared by both write paths.
Migrating an existing installation
Do not switchSTORED_OBJECTS_BACKEND before copying existing data. The migration task uses its own OBJECT_STORAGE_MIGRATION_* credentials, so source reads and destination writes do not depend on the app’s active provider:
plan is read-only. copy is resumable, verifies SHA-256 at the destination, and repairs a mismatched destination object. finalize refuses to run unless both reads and writes are explicitly paused, dataset uploads are idle, and GroupQueue has no pending, delayed, active, blocked, or staged durable references.
Reads must remain paused because finalization publishes destination storage addresses before the target-provider deployment is active. It publishes newer ClickHouse stored-object rows only after the final verified copy; it does not rewrite or delete history. No phase deletes source bytes.
After finalize succeeds, update STORED_OBJECTS_BACKEND and the active provider credentials while traffic remains paused, deploy that configuration, run verify, then resume traffic. A global migration excludes projects with private S3 buckets; they remain on their tenant-owned S3 destination.
Keep the source provider’s read credentials configured through the rollback window. Finalization rewrites stored-object addresses to the destination scheme, so a rollback that reverts STORED_OBJECTS_BACKEND and removes the destination provider’s credentials leaves every rewritten address unreadable. The bytes still exist on both providers (no phase deletes them), but the deployment can no longer serve that scheme. Concretely:
- S3 → Azure: after cutover, keep
S3_BUCKET_NAME+ S3 credentials configured (Helm:app.dataplane.legacyS3ReadBucket) so pre-migrations3://history stays readable. If you roll back to S3, keep theAZURE_BLOB_*settings configured (Helm:app.dataplane.legacyAzureRead: true) so the finalizedazure-blob://addresses stay readable. - Azure → S3: the mirror image. Keep
AZURE_BLOB_*(Helm:legacyAzureRead) after cutover, and keep the S3 settings if you roll back.
file:// from a local-storage era). Those rows are out of the migration’s scope, stay on their current addresses, and remain readable as long as that scheme’s backend stays configured.
Authenticating without an account key
AZURE_BLOB_AUTH_MODE selects how the app proves who it is. It defaults to sharedKey, so existing installs are unaffected.
Many organisations set allowSharedKeyAccess=false on the storage account, frequently enforced through Azure Policy. On such an account the shared-key mode does not degrade: every request is refused. Use one of the identity modes instead:
Whichever identity you use, grant it Storage Blob Data Contributor on the storage account or the specific container. The control-plane Contributor role does not grant data access. This is the single most common setup mistake, and it surfaces as a permission error rather than a sign-in error.
Three rules before you configure it:
- There is no fallback. If an identity mode is selected and the identity is missing or unauthorised, storage fails with an error and stays down until it is fixed. It never quietly reverts to an account key, because that would reverse your security decision at the moment you are least likely to notice.
- An account key alongside an identity mode is rejected. A key that would be silently ignored is worse than no key, since you would believe it was in use.
- Bearer tokens require HTTPS. A plaintext
AZURE_BLOB_ENDPOINTis refused before any token is sent.
app.dataplane.providers.azureBlob.authMode and enable global.serviceAccount (create: true plus the azure.workload.identity/client-id annotation). The chart refuses to render workloadIdentity without a ServiceAccount, since the identity would have no ServiceAccount to bind to. Federated Kubernetes identity is supported on AKS only, because the token injection depends on the Azure workload-identity webhook. On clusters without it, use managedIdentity or sharedKey.
On AKS, three things must line up or the token exchange fails with AADSTS70021 before any storage call is made. The app surfaces that code and this remedy in the error, but they are easier to get right up front:
- The federated identity credential’s issuer must match the cluster’s OIDC issuer exactly, including the trailing slash. Read it with
az aks show --query oidcIssuerProfile.issuerUrl. If the cluster is ever recreated the issuer changes and every federated credential silently stops working. - Its subject must be
system:serviceaccount:<namespace>:<service-account-name>, matched case-exactly against the ServiceAccount the chart creates. - Its audience must be
api://AzureADTokenExchange.
EMAIL_PROVIDER.
See Email for choosing a gateway and setting one up.
When
EMAIL_PROVIDER is not set, the gateway is inferred from whichever credentials are present (USE_AWS_SES first, then SENDGRID_API_KEY), so existing installs keep working untouched. smtp and resend must always be named explicitly.
SendGrid
AWS SES
Credentials come from the standard AWS chain (instance role, IRSA, or
AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY).
SMTP
Use this for an internal relay, Azure Communication Services, Office 365, Google Workspace, Postmark, Mailgun, Brevo, or any other gateway that speaks SMTP.Resend
Sending through an outbound proxy
Deployments whose only egress is a corporate HTTP proxy can set the standardHTTPS_PROXY (or HTTP_PROXY) and NO_PROXY variables. The ses, resend and sendgrid gateways all route their API calls through it; NO_PROXY entries are honoured, including leading-dot domain suffixes and *.
The smtp gateway ignores these variables on purpose, because an SMTP relay is normally an internal host that must be reached directly. Point it at your relay’s real address, or use a vendor’s SMTP endpoint if that is what the proxy allows out.
Neither the AWS SDK nor Node’s HTTP client reads
HTTPS_PROXY on its own, so LangWatch attaches
the proxy to the SES and Resend clients itself. SendGrid’s client is axios-based and reads the
variables directly. Without this wiring, a proxied deployment fails with a read ETIMEDOUT
reaching the provider.PII Redaction (Google DLP)
Model providers for evaluators, including Azure OpenAI, are configured per project in Settings > Model Providers, not through environment variables.Voice Agents
VOICE_CALL_MAX_SECONDS and VOICE_RUNS_MAX_CONCURRENT shape voice agent runs, which need FEATURE_FLAG_FORCE_ENABLE=release_voice_agents_enabled. The other three act on every worker boot, with the flag on or off: each worker listens on VOICE_WS_PORT. On a self-hosted production install it opens a cloudflared quick tunnel only when VOICE_TUNNEL=true and VOICE_PUBLIC_BASE_URL is unset.
Upgrade
The app and the workers never migrate on startup:pnpm task upgrade does, before they start, and they refuse to start until it has run. See Run the upgrade.
Exit codes, outcome codes and the log lines are in Upgrade Guide.
Cloud admin
Telemetry
Langy (optional)
Langy, the automated AI engineer, runs as a separate agent pod that the chart deploys by default (langyagent.chartManaged: true). It uses a shared LANGY_INTERNAL_SECRET (plus optional GITHUB_LANGY_* and mirror-lane vars). Those are documented on their own page.
See Langy environment variables for the full
LANGY_* and GITHUB_LANGY_* reference, and Setup for the
langyagent.chartManaged toggle and the LANGY_INTERNAL_SECRET secret.Helm Chart Mapping
When using the Helm chart, configuration is set invalues.yaml rather than environment variables directly. Here’s how key values map: