Skip to main content
LangWatch is configured through environment variables. How you set them depends on your deployment method:
  • Docker Compose: Set in your .env file
  • Helm chart: Set via values.yaml (the chart maps values to env vars automatically)
  • Raw Kubernetes: Set directly in your Deployment manifests
When using the Helm chart, you rarely need to set environment variables directly. The values.yaml file provides a structured way to configure everything. See the Helm chart mapping table below.

Core Configuration

Secrets

Never commit secrets to version control. In production, use a secrets manager (AWS Secrets Manager, HashiCorp Vault) or Kubernetes Secrets with secretKeyRef in the Helm chart.

Authentication

SSO Provider Variables

Each SSO provider requires specific variables. See SSO Configuration for detailed setup guides. Auth0: Azure AD: GitHub: GitLab: Google: Okta:

Services

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 via GET /api/files/:id.
  • Dataset uploads: persists rows uploaded through the dataset UI. Shares the same S3_BUCKET_NAME bucket.
When 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.
LANGWATCH_LOCAL_STORAGE_PATH is single-replica only. Multi-pod Kubernetes deployments must NOT rely on it: pods do not share a local filesystem, so a write from pod A is invisible to pod B and bytes vanish on every pod restart. Single-replica self-host installs (small footprints, hobbyist / air-gapped / pre-pilot deployments) can use it safely: the Helm chart refuses to render localFilesystem.enabled=true together with replicaCount > 1 so the misconfiguration can’t reach a cluster. Use S3_BUCKET_NAME (or the equivalent Helm app.dataplane.enabled toggle) for any horizontally-scaled deployment.

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 explicit STORED_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.
When STORED_OBJECTS_BACKEND=azure is set, AZURE_BLOB_ACCOUNT_NAME and AZURE_BLOB_CONTAINER are required, plus AZURE_BLOB_ACCOUNT_KEY when AZURE_BLOB_AUTH_MODE is sharedKey (the default). Under an identity mode the key must be absent (see below). An incomplete active Azure configuration fails loudly and never falls back to S3 or the local filesystem. Inactive Azure settings are validated only when an azure-blob:// address is used, so they cannot break global or private-bucket S3 traffic. Set AZURE_BLOB_ENDPOINT (chart: app.dataplane.providers.azureBlob.endpoint) for sovereign clouds or private endpoints; it defaults to https://{accountName}.blob.core.windows.net. Per-project Azure BYOC is tracked separately (issue #6088).

Migrating an existing installation

Do not switch STORED_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-migration s3:// history stays readable. If you roll back to S3, keep the AZURE_BLOB_* settings configured (Helm: app.dataplane.legacyAzureRead: true) so the finalized azure-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.
A rollback with the flag set is fully non-destructive: reads resolve per address scheme, and a reverse migration can later re-home the bytes. Also note: the plan reports rows addressed by a scheme outside both providers (for example 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 loudly 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_ENDPOINT is refused before any token is sent.
In the chart, set 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 nothing 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.
The pod label and the ServiceAccount annotation are both emitted by the chart; the federated credential itself is configured on the Azure side and is the piece the chart cannot do for you. The ServiceAccount and the workload-identity label are applied to the app and the workers only. Cron pods call the app over HTTP and never touch storage themselves, so giving them a Blob-capable token would grant every cron image storage access it has no use for. The same identity must be available to any out-of-band job that writes bytes, such as the dataset backfill task; run it with the same ServiceAccount as the app and workers.

Email

Notification emails (alerts, invites, scheduled reports, password resets) are sent through one gateway, selected with 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 standard HTTPS_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.

Evaluator Providers

Feature Flags

Telemetry

Langy (optional)

Langy, the automated AI engineer, runs as a separate agent pod that is off by default. Enabling it adds a langyagent.chartManaged toggle and 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 in values.yaml rather than environment variables directly. Here’s how key values map:
For production, use secretKeyRef instead of inline values. This references a Kubernetes Secret:
Last modified on August 15, 2026