Values reference
This page answers three questions that Configuration Reference does not, because it is organised by component rather than by decision:
- Which values do I have to set, and does that depend on how I am deploying?
- What does a complete values file look like, and which Secrets does it assume already exist?
- What do I change before I go live, and what breaks if I do not?
Use this page to write your values file. Use Configuration Reference to look up what an individual setting does.
What you are looking at
charts/align/values.yaml in the chart holds 28 top-level keys and 155 values whose
default is an empty string. An empty default is a decision left to you, so those 155 are the
set this page exists to triage: which are mandatory, which are mandatory only in some
deployment shapes, which you can leave alone, and which do nothing at all in the current
chart.
Both figures are re-derivable against the chart you were shipped, and both are compared against it in CI, so a value added to the chart without a line on this page fails the build:
# 28
grep -cE '^[a-zA-Z_][a-zA-Z0-9_]*:' charts/align/values.yaml
# 155
grep -vE '^[[:space:]]*#' charts/align/values.yaml | grep -cE ':[[:space:]]*""'
What those counts exclude: keys whose default is an empty list ([]), an empty map ({}) or
false. An empty list or map is usually "add nothing and it works"; a false is a feature
switch. Neither leaves you guessing whether the install will start, which is what the 155 do.
They also exclude two things worth knowing about:
- Values with a real default that still matter.
gateway.trustedProxyHops,gateway.schemaCheckModeandnetworkPolicies.enabledall ship with a value and all appear in the go-live checklist below. - Settings the chart's templates read that
values.yamldoes not declare. There are several,gateway.apiBaseUrlandgateway.authDemoModeamong them. They work when you set them and they are invisible to any sweep of the values file, so the ones that matter are named explicitly on this page.
Your onboarding pack ships a values file we have validated against your registry and your cluster shape. This page is that file explained, not a replacement for it. If the two disagree, the pack is the one we tested.
1. Before the values file: the Secrets
This comes first because it is what stops a first install. The recommended production setting
is secrets.create: false, which means the chart creates no Secrets at all and every
secrets.* value in your values file is ignored. That is the right choice, because a values
file ends up in git. It also means the Secrets have to exist before you run helm install, and
a missing one does not produce a log to read: the pod never starts, and kubectl describe pod
says CreateContainerConfigError.
These are the Secret keys a default install references without optional: true, so each one
must exist:
| Secret | Keys that must exist | When |
|---|---|---|
The name you put in database.secretName (align-database by default) | url, host, username, password | Always |
align-internal | jwt-secret, cookie-secret, encryption-key, service-auth-token | Always |
align-internal | master-encryption-key | Always in practice. It is optional: true at the Kubernetes layer, so the pod starts, and then the gateway refuses to boot because token encryption has no key. Installation creates it in Step 3's Minimal Secrets. |
align-oidc | issuer-url, client-id, client-secret | gateway.oidc.enabled is true |
The name you put in gateway.redis.secretName | the key you put in gateway.redis.secretKey | You set secretName rather than an inline URL |
align-llm | openai-api-key or anthropic-api-key | You use a hosted model |
Two things about those names. The internal, OAuth, LLM and Cognito Secrets are named after the
release: align-internal is what you get from helm install align, and helm install foo
wants foo-align-internal. The database Secret is the exception, because you name it yourself
in database.secretName.
app-url in the database Secret is optional: true and cannot exist at install time: the
align_app role is created by a migration, so there is no password to put there until the first
install has run. Add it during the RLS cutover. Until then the gateway
connects as the owner, which is the go-live item below.
Installation has the kubectl create secret commands.
Secrets covers sealed secrets and External Secrets Operator.
2. Required values, by deployment shape
Every install
| Value | Why | What happens if you skip it |
|---|---|---|
global.imageRegistry | The registry root your images come from | The chart refuses to render, naming the image it would have produced. An empty value gives a bare image name, and Docker resolves a bare name against Docker Hub rather than anywhere Align controls. (global.allowUnqualifiedImages: true disables that refusal. Do not set it.) |
global.imageTag | Pins the release you tested | Resolves to latest, which is not a release. A pod restart can pull a different build than the one you validated. |
database.secretName | Names the Secret holding your database credentials | Defaults to align-database. If no Secret by that name exists, no pod starts: CreateContainerConfigError, and no container logs to read. |
gateway.signupClosedMessage | What a person is told when they first sign in with a work email and your organisation has no workspace for that domain | Optional. Empty shows a neutral message that asks them to contact their workspace administrator. Set it to name your own contact. |
gateway.sso.groupRoleMapping | JSON that maps a directory group name to org_admin, member or viewer | Optional. Empty maps no group, so every SSO user is a member. Renders SSO_GROUP_ROLE_MAPPING. |
gateway.sso.groupsClaim | The token claim that lists a person's groups | Optional. Empty uses groups. Renders SSO_GROUPS_CLAIM. |
gateway.sso.groupFunctionMapping | JSON that maps a directory group name to an onboarding work function | Optional. Only pre-fills a hint, never a role. Renders SSO_GROUP_FUNCTION_MAPPING. |
gateway.saml.enabled | Turns on SAML 2.0 sign-in | Default false: no /saml route exists. Needs gateway.apiBaseUrl. |
gateway.saml.spEntityId | The service provider entity id | Optional. Empty uses <apiBaseUrl>/saml/metadata. |
gateway.saml.spAcsUrl | The assertion consumer service URL | Optional. Empty uses <apiBaseUrl>/saml/acs. The IdP checks it exactly. |
gateway.saml.groupsAttribute | The assertion attribute that lists a person's groups | Optional. Empty uses groups. |
gateway.saml.defaultTenantId | The tenant uuid the SAML sign-in button and /saml/login use when no tenant is named | Set it on a self-host that wants the SAML sign-in button and the SAML admin screen. Empty (the default): no SAML button, /saml/login with no ?tenant= stays 400, and enforcing SSO with SAML as the only provider is refused. Must be a UUID; the chart fails the render otherwise. |
gateway.oauthAuthorizationServer.issuerUrl | gateway.oauthAuthorizationServer.enabled is true | The chart refuses to render. Set it to the public API URL, with no trailing slash. It is the OAuth issuer that Claude and ChatGPT discover, so it must be reachable from the internet over https. |
gateway.frontendUrl | Where a human opens Align: decision links, magic-link emails, OAuth and SSO redirects, the cookie domain | See its row in the go-live checklist. Leave it empty only when ui.ingress.enabled is true and its first host is the URL your users type. Otherwise you get http://align.local and every link is broken. |
More than one gateway replica
Scaling the gateway out needs a shared session store, and this is a security property rather than a performance one.
| Value | Why | What happens if you skip it |
|---|---|---|
gateway.redis.url, or gateway.redis.secretName plus gateway.redis.secretKey | The session-revocation marker lives in the cache backend | The chart refuses to render gateway.replicaCount above 1, and refuses an autoscaler whose maxReplicas is above 1. Without the refusal, a session revoked through the admin API would be revoked only on the pod that served that call and would stay live on every other pod until the token's own 7-day expiry. No pod's marker survives a restart either, so revocation is not durable at any replica count without a shared store. |
gateway.allowUnsharedSessionStore | Opts out of that refusal | Off by default, and there is no good reason to turn it on for an install reachable from the internet. |
Any Redis-compatible service works: ElastiCache, Memorystore, Azure Cache, Dragonfly, KeyDB.
Supplying the URL through gateway.extraEnv as REDIS_URL also satisfies the check, because
the deployment renders extraEnv verbatim.
brain.replicaCount and ui.replicaCount default to 2 and need nothing extra. Neither holds
per-pod session state.
On disruption budgets, since the defaults are asymmetric: gateway.pdb.enabled, brain.pdb
and ui.pdb are on, and the gateway's is skipped rather than refused below two replicas, so
a single-replica install is still drainable. Connectors have no pdb key at all and default to
off, so an enabled connector has no eviction protection until you add one. A connector PDB at
one replica is refused, because minAvailable: 1 against a single pod blocks every
voluntary eviction, including a node drain.
Single sign-on
Pick one of the two paths. In production the gateway does not start with neither. See No identity provider for email sign-in.
| Path | Values |
|---|---|
| Your own identity provider (Azure AD, Okta, Keycloak, any OIDC) | gateway.oidc.enabled, plus gateway.oidc.scopes if you need something other than openid email profile, plus gateway.oidc.defaultTenantId for a single-tenant install. Credentials go in the align-oidc Secret, not in values. |
| Cognito, where we host the user pool | gateway.cognito.googleIdpName and gateway.cognito.azureIdpName only if your pool's identity provider names differ from Google and Microsoft. Empty means the code defaults apply. |
Social and enterprise sign-in credentials, if you use them, are
secrets.oauth.google.clientId, secrets.oauth.google.clientSecret,
secrets.oauth.azure.clientId, secrets.oauth.azure.clientSecret and
secrets.oauth.azure.tenantId. See OAuth Apps.
Connectors you turn on
Each connector needs its own OAuth credentials, and an enabled connector without them starts and
then fails at the point a user tries to connect it, not at install. One provider app can cover
two connectors: a single Atlassian app serves both Jira and Confluence. The
credential table below is the authority on which values each one
needs. The connector switches are
connectors.slack.enabled, connectors.teams.enabled, connectors.jira.enabled,
connectors.github.enabled, connectors.confluence.enabled, connectors.zoom.enabled,
connectors.linear.enabled, connectors.azureDevOps.enabled, connectors.m365Documents.enabled
and connectors.align.enabled. Azure DevOps and M365 Documents default to true.
Redirect URIs come from gateway.apiBaseUrl, which the chart reads and values.yaml does
not declare. Every connector's callback URL is derived from it, falling back to your
gateway.ingress host. Set it when those differ, and register the exact derived URL in each
provider app: see OAuth Apps.
The same value is also the gateway's public address, rendered as the PUBLIC_GATEWAY_URL
environment variable, but only when you named a real address: gateway.apiBaseUrl, or
gateway.ingress.enabled: true with your host. A default install renders nothing, so org
invite links carry no gw= parameter and share approval stays closed until you set one.
Two things read it. Approving a share needs it, and org invite links carry it as gw= so that
align join '<link>' knows which server it is joining. What is published is the origin only,
so it must be https (plain http is accepted only for localhost) with no user name, path,
query or fragment. A value that breaks those rules does not stop the pod: the gateway logs a
warning naming PUBLIC_GATEWAY_URL, invite links carry no gw= and invite-peek names no
gateway. The one exception is production with SHARE_APPROVAL_MODE=required, where it refuses
to start. FRONTEND_URL, from gateway.frontendUrl, is the web address the same links point
at, so it has to be the URL your users type.
connectors.align is the MCP endpoint your agents talk to. If you expose it, it needs
connectors.align.ingress.className and a host. Set connectors.align.defaultTenantId for a
single-tenant install: left empty it does not mean "no tenant", it means the fixed tenant id
00000000-0000-0000-0000-000000000001.
Secrets held in a cloud secret manager
externalSecrets.enabled turns on External Secrets Operator integration, which templates
ExternalSecret objects for the database, internal, LLM and Slack OAuth Secrets. It is not a
drop-in replacement for pre-creating all of them. ESO must already be installed.
The fields the chart actually reads are externalSecrets.secretStore.name,
externalSecrets.secretStore.kind, externalSecrets.refreshInterval and
externalSecrets.aws.secretPath, and the last one is used as the remote key prefix for every
provider, not just AWS. The per-provider fields are read by no template today: see
section 5. See Secrets.
An LLM that is not a cloud API
secrets.llm.openaiApiKey and secrets.llm.anthropicApiKey are how the brain reaches a hosted
model. To run inference in your own cluster instead, use gpu.devicePlugin.enabled and
gpu.llmServer.enabled, with gpu.llmServer.quantization and gpu.llmServer.maxModelLen if
the model needs them. See LLM Configuration.
If you run a model in your cluster to keep inference inside your network, note that
secrets.llm.openaiApiKey is also what sends embedding text to OpenAI. Natural-language search needs it.
Behind an outbound proxy
global.proxy.httpProxy, global.proxy.httpsProxy and global.proxy.noProxy reach the
gateway, the brain, the UI and every connector, which is where outbound calls to Slack,
Jira, GitHub and a model API come from. Put your in-cluster service names and the cluster DNS
suffix in noProxy, or pod-to-pod calls go out through the proxy and fail.
They do not reach the migrations job, the telemetry CronJobs, the OpenTelemetry collector or the GPU inference server: those templates do not include the proxy helper. If your network requires every egress to go through the proxy, those four are the ones to check by hand.
The chart brings its own Postgres, Redis and secrets (pilots)
postgresql, redis and rlsCutover are three opt-in switches, all off by default. They are
for a pilot or a single-VM install, not for production. The walk-through is in
Installation.
postgresql.enabledruns a single-replica pgvector StatefulSet and writes the database Secret. It refuses to render withoutrlsCutover.enabled, and refuses to overwrite an existing database Secret it did not create. It is created once, at the first install, and uninstalling the release leaves it behind.redis.enabledruns a password-protected Redis and wires it into the gateway, which satisfies the more-than-one-replica rule above.gateway.redis.urlandgateway.redis.secretNamestill win.rlsCutover.enabledsets thealign_apppassword after migrations and checks the connection before the gateway starts. It needsapp-passwordandapp-urlin the database Secret, whichpostgresql.enabledwrites.- With
secrets.create: truethe chart generatesmaster-encryption-keywhen you give none, and keeps it across upgrades. Back it up.
Generated passwords and keys are read back from the cluster, so these switches need the Helm CLI. Under a GitOps controller, or when you only render the chart, set the passwords and the master key yourself.
Database TLS
Use global.rdsSSL.enabled. It mounts the RDS CA bundle and points the gateway and the brain at
it, and despite its name it is the only implemented switch.
global.databaseSSL.enabled, global.databaseSSL.provider,
global.databaseSSL.customCA.secretName and global.databaseSSL.customCA.secretKey are read by
no template in the current chart, so setting them has no effect and no custom CA bundle is
ever mounted. Use global.rdsSSL.enabled.
3. A complete starter values.yaml
This is a whole file, not a fragment. Every line you have to decide is either filled in or marked. Copy it, replace every REPLACE_ME, delete the sections you do not need, and create the Secrets from section 1 first.
# ---------------------------------------------------------------------------
# Align, self-hosted. A complete starter values file.
#
# Replace every REPLACE_ME. Anything not listed here keeps its chart default;
# see /self-host/configuration for what each default is. Values the current
# chart reads NOWHERE are deliberately left out of this file - they are listed
# on the page instead, so nobody sets one and expects an effect.
# ---------------------------------------------------------------------------
global:
environment: production
# REQUIRED. The registry ROOT, without the `align/` path segment the chart
# already carries in each image repository. The chart refuses to render if this
# is empty.
# Align registry: the $ALIGN_REGISTRY host issued at onboarding
# AWS ECR: 123456789.dkr.ecr.us-east-1.amazonaws.com
# Azure ACR: myregistry.azurecr.io
# GCP AR: us-docker.pkg.dev/my-project
imageRegistry: REPLACE_ME
# Pin the release you tested. Empty means "latest", which is not a release.
#
# This does NOT reach the connectors. Each one carries its own `image.tag: latest`
# in values.yaml, and a per-component tag wins over the global one, so a
# connector keeps pulling `latest` however you set this. Pin each enabled
# connector explicitly below.
imageTag: REPLACE_ME
# The docker-registry Secret you created. /self-host/install calls these
# ecr-credentials / acr-credentials / gcr-credentials - use the name you
# actually created.
imagePullSecrets:
- name: REPLACE_ME
# Mounts the RDS CA bundle. The only implemented database-TLS switch; the
# global.databaseSSL block in values.yaml is read by no template.
rdsSSL:
enabled: true
# Outbound HTTP proxy, if your cluster has no direct egress. Put in-cluster
# names and the cluster DNS suffix in noProxy or pod-to-pod traffic goes
# through the proxy and fails.
proxy:
httpProxy: ""
httpsProxy: ""
noProxy: ""
# The Secret you created with database credentials. Keys url, host, username and
# password must exist. app-url comes later, at the RLS cutover.
database:
secretName: align-database
serviceAccount:
create: true
name: align
annotations: {}
# Self-host licensing. Set enabled: true once we have issued your license and you
# have created the Secret named below - see /self-host/licensing.
license:
enabled: false
secretName: align-license
secretKey: license.jwt
# The daily licence check-in (see /self-host/licensing). false = no call.
checkin:
enabled: true
url: ""
gateway:
enabled: true
# Above 1 you MUST set gateway.redis below. The chart refuses the combination.
replicaCount: 2
redis:
# Either an inline URL, or a Secret. Any Redis-compatible service.
url: ""
secretName: align-redis
secretKey: url
# How many proxy hops are in front of the gateway, as TRUSTED_PROXY_HOPS. One
# ingress is 1. An ingress behind a CDN is 2. request.ip, and therefore every
# address in the audit log, resolves through this. Count them and match. The
# chart caps it at 10. Do not write a leading zero: unquoted `010` is YAML octal
# and reaches the gateway as 8, which the chart CANNOT catch because YAML parses
# it before any template runs. Only the quoted form (`"08"`) is refused.
trustedProxyHops: 1
# Where humans open Align. Derived from ui.ingress ONLY when that ingress is
# enabled and has a host; otherwise it falls back to http://align.local and
# every link breaks. Set it explicitly unless you are sure.
frontendUrl: https://align.REPLACE_ME
# Read by the chart, absent from values.yaml. Every connector's OAuth redirect
# URI is derived from this. Set it when the API is reached at a URL other than
# the ingress host below, and register the derived URIs in each provider app.
# apiBaseUrl: https://api.align.REPLACE_ME
ingress:
enabled: true
className: nginx
hosts:
- host: api.align.REPLACE_ME
paths:
- path: /
pathType: Prefix
tls:
- secretName: api-align-tls
hosts:
- api.align.REPLACE_ME
# Keep `enforce`. It refuses to boot when the database is behind the migrations
# this build requires, which is the condition that otherwise corrupts writes.
# If you ever need the escape hatch, `"off"` must be QUOTED: bare off is a YAML
# boolean and the chart refuses it rather than silently ignoring you.
schemaCheckMode: enforce
# Distributed job processing is selected by the PRESENCE of these queue URLs,
# not by a provider name. With none set, Discover jobs run on an in-process
# queue and queued work is lost on a pod restart.
sqs:
importJobQueueUrl: ""
bulkApprovalQueueUrl: ""
bulkDeleteQueueUrl: ""
# Your own identity provider. Credentials go in the align-oidc Secret.
oidc:
enabled: true
scopes: ""
defaultTenantId: ""
# Only if you use the Cognito path AND your pool's provider names differ from
# the code defaults (Google / Microsoft).
cognito:
googleIdpName: ""
azureIdpName: ""
# Connection pooling sidecar. Turn on for high load rather than day one.
pgbouncer:
enabled: false
# Per-component image tag. Leave empty so it follows global.imageTag.
image:
tag: ""
# Cloud access for the gateway specifically (IRSA, workload identity).
serviceAccount:
create: false
name: ""
# Anything without a first-class value. Keep this short: a setting that belongs
# in a review conversation belongs in a named value instead. Do NOT put
# TRUSTED_PROXY_HOPS here as well - the chart refuses the pair, because
# extraEnv renders last and would silently win.
extraEnv: []
brain:
enabled: true
replicaCount: 2
image:
tag: ""
ui:
enabled: true
replicaCount: 2
image:
tag: ""
ingress:
enabled: true
className: nginx
hosts:
- host: align.REPLACE_ME
paths:
- path: /
pathType: Prefix
tls:
- secretName: align-tls
hosts:
- align.REPLACE_ME
# Turn off what you are not using. An enabled connector with no OAuth credentials
# starts, and fails when a user tries to connect it.
#
# PIN THE TAG on every connector you enable. Unlike the other components, each
# connector ships `image.tag: latest` in values.yaml, and a per-component tag beats
# global.imageTag - so leaving this out means a pod restart can pull a connector
# build you never tested, whatever you pinned globally. Use the same release.
connectors:
slack:
enabled: true
image:
tag: REPLACE_ME
teams:
enabled: false
jira:
enabled: true
image:
tag: REPLACE_ME
github:
enabled: true
image:
tag: REPLACE_ME
confluence:
enabled: false
zoom:
enabled: false
linear:
enabled: false
# The MCP endpoint your coding agents talk to. An empty defaultTenantId means
# the fixed id 00000000-0000-0000-0000-000000000001, not "no tenant".
align:
enabled: true
defaultTenantId: ""
image:
tag: REPLACE_ME
ingress:
enabled: false
className: ""
hosts: []
tls: []
# `create: false` is the production path and means the chart creates NO Secrets.
# Every secrets.* value below is then ignored, including the master key - supply
# these through the Secrets in section 1 instead. The block is here so you can
# see what exists, not so you can fill it in.
secrets:
create: false
internal:
# Ignored while create is false. The real home for this is the
# master-encryption-key key of the align-internal Secret.
masterEncryptionKey: ""
jwtSecret: ""
cookieSecret: ""
encryptionKey: ""
serviceAuthToken: ""
mcpAlignToken: ""
# One block per connector you enabled. See /self-host/oauth-apps. Zoom has no
# values.yaml block at all: the gateway reads its credentials from the
# align-oauth-zoom Secret (client-id, client-secret, webhook-secret, and robot-jid
# for Team Chat).
oauth:
slack:
clientId: ""
clientSecret: ""
signingSecret: ""
slackPersonal:
clientId: ""
clientSecret: ""
github:
clientId: ""
clientSecret: ""
appId: ""
appPrivateKey: ""
appClientId: ""
appClientSecret: ""
personalClientId: ""
personalClientSecret: ""
atlassian:
clientId: ""
clientSecret: ""
linear:
clientId: ""
clientSecret: ""
gitlab:
clientId: ""
clientSecret: ""
notion:
clientId: ""
clientSecret: ""
teams:
clientId: ""
clientSecret: ""
botAppId: ""
botAppPassword: ""
botAppTenantId: ""
google:
clientId: ""
clientSecret: ""
azure:
clientId: ""
clientSecret: ""
tenantId: ""
llm:
existingSecret: ""
openaiApiKey: ""
anthropicApiKey: ""
# In-cluster TLS certificate generation, for local development. TLS normally
# terminates at your ingress.
tls:
create: false
secretName: ""
commonName: ""
altNames: []
# Align's hosted billing. No effect in a self-hosted install.
stripe:
secretKey: ""
webhookSecret: ""
priceIdTeam: ""
# External Secrets Operator. Only secretStore.name, secretStore.kind,
# refreshInterval and aws.secretPath are read by the chart, and aws.secretPath is
# the remote key prefix for every provider rather than just AWS. The
# per-provider fields (provider, aws.region, azure.vaultUrl, azure.tenantId,
# gcp.projectId, vault.server) are read by no template today, so they are left
# out of this file.
externalSecrets:
enabled: false
secretStore:
name: ""
kind: ClusterSecretStore
refreshInterval: 1h
aws:
secretPath: align/production
# Usage telemetry into YOUR database. Nothing leaves your cluster.
# telemetry.export.prometheus and telemetry.export.datadog are read by no
# template today.
telemetry:
enabled: true
# Database migrations run as a Helm pre-upgrade hook. The image tag must match
# the gateway's or the database ends up behind the application; the chart
# enforces that lockstep.
migrations:
enabled: true
image:
tag: ""
# Upper bound on the migrations hook Job. See the go-live row.
activeDeadlineSeconds: 900
# Default-deny network posture. Only enforced under a policy-capable CNI.
# Validate enforcement on a non-production cluster first. See /self-host/network.
networkPolicies:
enabled: false
ingress:
fromIngressController:
enabled: false
allowedIngressCIDRs: []
allowedExternalCIDRs: []
allowDNS: true
# Prometheus Operator PodMonitor for infrastructure metrics.
podMonitor:
enabled: false
# Non-root, read-only-root-filesystem pod security context. Keep it on.
securityContext:
enabled: true
# In-cluster GPU inference instead of a cloud model API. See /self-host/llm.
gpu:
devicePlugin:
enabled: false
llmServer:
enabled: false
quantization: ""
maxModelLen: ""
# OpenTelemetry tracing. Off by default. Enabling runs a collector in your
# cluster. The credential is NOT taken from values: the collector reads it from a
# Secret named align-otel, and that reference is required, so enabling a backend
# without the Secret gives you a collector pod that will not start.
observability:
enabled: false
grafanaCloud:
enabled: false
otlpEndpoint: ""
authToken: ""
datadog:
enabled: false
apiKey: ""
site: datadoghq.com
prometheus:
enabled: false
# Cluster placement, applied to every Align pod.
nodeSelector: {}
tolerations: []
affinity: {}
# What a connector captures for a tenant with no capture setting of its own. off saves nothing until
# someone opts a connector in. propose saves only what a person confirms. auto leaves each connector's
# own default.
capture:
defaultMode: "off" # keep the quotes: unquoted, YAML reads off as false
4. Go-live checklist
These are the chart defaults we would raise in your security review ourselves. Each row names the failure mode, not just the recommendation, because every one of them fails quietly.
| Setting | Chart default | Do this before go-live | What goes wrong if you do not |
|---|---|---|---|
networkPolicies.enabled | false, so an install that changes nothing has no default-deny policy | Confirm your CNI enforces NetworkPolicy, then turn this on, then confirm pod-to-pod traffic still flows on a non-production cluster first | A cluster whose CNI ignores NetworkPolicy accepts the objects silently. You get the appearance of segmentation and none of it. On EKS with the AWS VPC CNI that means the node agent running with network policy enabled. |
The align_app database role | The application connects as the database owner | Complete the RLS cutover and add app-url to the database Secret | In PostgreSQL a table owner bypasses row-level security by design. The policies are present, FORCE-enabled and tested, and they are not yet the operative boundary. Application-layer tenant scoping is holding the line alone until you cut over. |
gateway.trustedProxyHops, which sets TRUSTED_PROXY_HOPS | 1, which assumes one proxy in front of the gateway | Count the proxies actually in front of it and match. One ingress is 1. An ingress behind a CDN is 2. | Too low and your audit log records your own ingress rather than the caller. Too high and a caller can forge their own address by prepending an x-forwarded-for entry. It is a number to match reality, not one to raise for safety. Neither direction errors. The chart caps it at 10 and refuses a leading zero, but within that range nothing can tell a wrong count from a right one. |
The token encryption key (master-encryption-key in the align-internal Secret) | No default. None is generated unless secrets.create is true, which generates one and keeps it | Generate it, store it in your secret manager, plan rotation | The gateway refuses to boot, so this one fails loudly unless gateway.authDemoMode is true. Rotation needs no downtime but does need planning: Key Rotation. |
gateway.authDemoMode | Unset, and absent from values.yaml | Never set it in a real install | The chart reads it even though the values file does not list it. Setting it permits storing OAuth tokens in plaintext and turns the boot refusal above into a warning in the log. It is the most security-relevant value an operator can set by accident. |
gateway.redis.url or gateway.redis.secretName, above one replica | Both empty | Set one before raising gateway.replicaCount | The chart refuses the combination, so this also fails loudly. The quiet failure is gateway.allowUnsharedSessionStore: true, which turns the refusal off and leaves session revocation per-pod and not durable across a restart. |
gateway.frontendUrl | Empty, derived from ui.ingress | Set it explicitly, or confirm ui.ingress.enabled is true and its first host is the URL your users type | With no ingress host to derive from it becomes http://align.local. Every decision link, magic-link email and OAuth redirect points there, and nothing errors. This bites exactly the installs that front the UI with something other than the chart's Ingress. |
gateway.schemaCheckMode | enforce | Leave it | warn and off let the gateway serve against a database behind the migrations this build requires, which corrupts writes with nothing failing. |
gateway.serviceTenantBinding | "log" | Leave it, and read its log lines before any upgrade that turns it to enforce | It decides whether a connector's service token may name a tenant that connector does not serve. log refuses nothing and writes one [serviceTenantBinding] would refuse line per request it would refuse. enforce refuses those requests from the Slack, Teams, GitHub and mcp-align tokens with 403. off stops checking. A bare off must be QUOTED, and any other value fails the render. |
gateway.serviceTokenPinnedTenantId | Empty | On a single-tenant install, set it to your tenant id | With it set, the shared service token is checked against that one tenant rather than "any tenant with a connection". Today that check only logs. Leave it empty on a multi-tenant install, or every other tenant's shared-token traffic logs as a would-be refusal. |
migrations.activeDeadlineSeconds | 900 | Leave it, unless a release ships a migration you expect to run longer. Then raise it for that upgrade. | Lowered too far, a slow migration is killed mid-run and the upgrade fails. Raised far beyond your longest migration, a migrations pod that can never start (a missing ServiceAccount, an unpullable image) holds the upgrade open that long before it fails. The chart refuses zero, a negative value or null, so the hook is always bounded. Align's own migration hook runs in about 13 seconds. |
global.imageTag | Empty, resolving to latest | Pin the release you tested | latest is not a release. A pod restart can pull a different build than the one you validated, and you cannot say afterwards which one ran. |
connectors.<name>.image.tag | latest, explicitly, for every connector | Pin each enabled connector to the same release | This is the trap inside the row above. A per-component tag beats global.imageTag, and every connector carries image.tag: latest in the shipped values file, so pinning the global tag leaves every connector on a floating tag. Verified by rendering the chart: global.imageTag: v9.9.9 produces align/gateway:v9.9.9 and align/connector-slack:latest in the same install. |
gateway.sqs.importJobQueueUrl and its two siblings | Empty | Set them if you want distributed job processing | With none set, Discover work runs on an in-process queue and queued jobs are lost on a pod restart, with no error and no retry. |
securityContext.enabled | true | Leave it | Turning it off drops the non-root user and the read-only root filesystem. |
global.allowUnqualifiedImages | false | Leave it | It disables the registry refusal in the first row, so an unqualified image name resolves against Docker Hub. |
Five things the checklist cannot cover, stated here rather than left out:
- In-cluster traffic is plain HTTP. Gateway to brain and gateway to connector calls are unencrypted inside your cluster and authenticated with a shared service token. TLS terminates at your ingress. Mutual TLS is documented guidance, not shipped on by default, and the default-deny NetworkPolicy above is off until you enable it.
- A historical import holds raw content for at least 33 days. Batch rows keep the full fetched items so a long import can resume. They purge 33 days after the job last changed, for jobs that completed or failed. A cancelled job is never purged. See Data Retention.
- The raw message discard is best effort, not a guarantee. Raw text rides inside the decision row until analysis finishes and is blanked then. If analysis dies mid-run the copy persists until someone notices.
- Embeddings are not covered by PII redaction. The redaction choke point covers the completion, stream and tool-call paths. The embeddings path sits outside it.
- All of the above stays in your own database. None of it reaches Align.
First scan: name the sources, see a sample (scan.requirePreview)
scan.requirePreview defaults to true, so a self-hosted install enforces this: before the
first scan of a connector you name the sources in scope, and Align shows you a sample of what it
would read before it reads it. Three things are worth knowing before you rely on it.
- What the sample is. A bounded list of titles and links for the sources you named, at most
10 per source and at most 20 sources, plus a count. For Slack it is a message count and no
titles. It never shows message or page text, makes no model call and stores nothing. Calling
POST /import/jobs/previewis what shows it. - What it gates. The first scan is refused with a 409 until a sample for exactly the same
sources has been shown to the same person (it is remembered for an hour). If the gateway
cannot record or read that, the scan is refused with a 503, never allowed. It also stops a
first scan from starting on its own, since nobody named its sources. It holds the first-scan
screen,
POST /import/first-scan, and the Discover routes that start a scan:POST /import/jobs,POST /import/jobs/bulk-startandPOST /import/jobs/:jobId/start(a first-scan job is not asked again at start, it passed when it was made). A Discover scan of a previewable connector therefore needs named sources too. The preview route allows 30 previews per tenant per minute, and answers 503 if it cannot count them.POST /import/jobs/dry-run, the cost estimate, and a retry of a job that already ran are not held to the preview. - What it covers. Slack, GitHub, Confluence, Jira (Cloud and Data Center, see below) and Notion. For Notion you do not name pages in
Align. The scope is whatever you share with the Notion app on Notion's own consent screen, and
the sample lists what is shared. Teams, Linear, Zoom and Azure DevOps have no preview
yet, so their scans is not held up. Set
scan.requirePreview: falseto turn the whole requirement off. With more than one gateway replica, setgateway.redisas well: the record lives in the gateway cache, and without a shared Redis a preview shown by one replica is not seen by another.
Jira Data Center and Server in the preview
The Jira preview runs one search for the project you name (GET /rest/api/2/search, summaries
only, one past the cap of 10) against your Jira Data Center or Server base URL, with a personal
access token sent as Authorization: Bearer <token>. Jira 8.14 or later is needed for personal
access tokens. Four rules apply.
- The connection must record its type. Align picks Cloud or Data Center from the type the
connection records (
deployment_type:cloudordatacenter), never from the host name. A Jira connection that has a base URL, no cloud id and no recorded type gets a 409jira_connection_type_unknownand no request is sent. Today only the Atlassian OAuth connect writes a Jira connection, and it records a cloud id and no type, so a Data Center connection has to be stored withdeployment_type: datacenterfor the preview to use this path. - The base URL is checked before a credential is sent. It must be https, with no user name,
password, query or fragment, and a public address. A private-network Jira host has to be listed
by you. Set
JIRA_DC_ENDPOINT_ALLOWLISTon the gateway (host names, IPs or CIDRs, separated by commas, throughgateway.extraEnv). The listing rules are the same as forCUSTOM_LLM_ENDPOINT_ALLOWLISTabove: a listed name may resolve into private space, never to loopback, link-local or the cloud metadata addresses. Plain http needsJIRA_DC_ENDPOINT_ALLOW_HTTP=true. A refused base URL is a 409jira_base_url_not_allowed. - No redirect is followed. A 3xx answer from Jira is an error, so the token cannot be sent to another host. The gateway checks the addresses a name resolves to when it connects, not only when it reads the URL.
- Ports are not restricted. A Data Center base URL may use any port (8080 and 8443 are common). The preview sends one request to it, with the connection's own token, and returns titles only, never the raw reply.
- The count. Jira returns a
total, and the preview shows it. If your instance leaves it out, the count is shown as "at least" what one page held. - A private certificate authority. The gateway uses Node's certificate store. Point
NODE_EXTRA_CA_CERTSat your CA file ingateway.extraEnv.
This path has not been run against a live Jira Data Center instance. Before you rely on it, run one preview against a test project and check that the token form, the count and the TLS chain work on your install.
5. What every empty default means
Every value in the chart whose default is an empty string, grouped, so you can tell at a glance whether it is yours to fill in. "Required when" says what makes it mandatory.
Some rows say read by no template. Those are declared in values.yaml and wired to nothing
in the current chart, so setting them has no effect. They are listed rather than hidden, because
an operator reading 155 empty strings needs to know which ones do nothing.
Registry, images and cluster-wide settings
| Value | Required when | If left empty |
|---|---|---|
global.imageRegistry | Always | The chart refuses to render. |
global.imageTag | Always in practice | Resolves to latest, which is not a release and is not reproducible. |
global.proxy.httpProxy, global.proxy.httpsProxy, global.proxy.noProxy | Egress goes through a proxy | Pods make direct outbound calls, which your network may drop. |
global.customLlmEndpoint.allowlist (with global.customLlmEndpoint.allowHttp) | A tenant points the custom LLM provider at an in-cluster model server (vLLM, Ollama) | Only public https hosts are allowed, so an in-cluster address is refused when it is saved and when the Brain connects. See Allowing an in-cluster endpoint. |
global.databaseSSL.provider, global.databaseSSL.customCA.secretName | Never | Read by no template. Use global.rdsSSL.enabled. No custom CA bundle is mounted by the current chart under any setting. |
license.checkin.url | Never | The gateway calls the built-in licence service address (see Licensing). Set it only if we give you a different address. |
gateway.image.tag, brain.image.tag, ui.image.tag, migrations.image.tag | Never | Each follows global.imageTag, which is what you want. Set one only to run a different tag for that service deliberately. The connectors are not like this: theirs default to latest rather than empty, so they do not follow the global tag. See the go-live row. |
Bundled Postgres and Redis
| Value | Required when | If left empty |
|---|---|---|
postgresql.auth.password, postgresql.auth.appPassword | Never with the Helm CLI. Always under a GitOps controller or a plain render. | Generated on first install and kept. Where the chart cannot read them back, every render makes new ones while the data volume keeps the first. |
postgresql.persistence.storageClass | Never | The cluster default storage class. |
redis.auth.password | Never with the Helm CLI | Generated on first install and kept. |
redis.persistence.storageClass | Never | The cluster default storage class. Only read when redis.persistence.enabled is true. |
Embeddings
| Value | Required when | If left empty |
|---|---|---|
embedding.profile | Chat runs in your network (gpu.llmServer.enabled), or you want embeddings off public OpenAI | The default hosted profile runs and embeddings go to OpenAI. With gpu.llmServer.enabled the chart refuses to render until you set a profile or embedding.allowOpenAI=true. |
embedding.requireChoice | Never: it defaults to true. Set false only if you accept embeddings going to OpenAI whenever a tenant saves Anthropic or a custom chat endpoint | Stays true: PUT /tenant/llm-config returns 400 embedding_choice_required for non-OpenAI chat until embedding.profile is set, the tenant has a non-OpenAI embedding provider, or embedding.allowOpenAI=true is set. OpenAI chat is never refused. |
embedding.azureOpenai.endpoint, embedding.azureOpenai.deployment | embedding.profile is azure-openai-te3-small-384 | The chart refuses to render. deployment is the name of your text-embedding-3-small deployment, not the model name. |
embedding.azureOpenai.apiVersion | Never | The Brain uses its built-in default API version (2024-10-21). Set it if your Azure resource needs a different one. |
embedding.compatible.baseUrl, embedding.compatible.model | embedding.profile is openai-compatible-384 | The chart refuses to render. The server must return 384-wide vectors. |
Capture default
| Value | Required when | If left at the default |
|---|---|---|
capture.defaultMode | On every upgrade: the chart refuses to render until you set it. On a fresh install, never. Values: "off", "propose", "auto" or "registry" (quote them) | A fresh install behaves as "off": it captures nothing until a tenant admin gives a connector its own capture setting, which beats this value for that connector only. An upgrade with it unset fails the render, so a running install never changes what it captures by accident. (This guard sees helm upgrade. Argo CD, Flux and a piped helm template render as an install, so an install deployed that way gets "off" when this chart version reaches it. Set capture.defaultMode before you move to this version.) "registry" or "auto" keeps each connector's built-in default. The value only lowers, so no licence is checked. |
Gateway
| Value | Required when | If left empty |
|---|---|---|
gateway.frontendUrl | ui.ingress is disabled or has no host, or Align is reached at a different URL | Falls back to http://align.local and every link is broken. See the go-live row. |
gateway.redis.url, gateway.redis.secretName | replicaCount above 1, or an autoscaler above 1 | The chart refuses to render. Session revocation would otherwise be per-pod. |
gateway.queue.provider | Never. Set memory to accept an in-memory queue | Auto: SQS if the three gateway.sqs URLs are set, else Redis if gateway.redis is configured. With neither, the production gateway refuses to start (the in-memory queue loses scans and bulk work on restart). See the job queue. |
gateway.sqs.importJobQueueUrl, gateway.sqs.bulkApprovalQueueUrl, gateway.sqs.bulkDeleteQueueUrl | You want distributed job processing | Jobs run on an in-process queue and queued work is lost on a pod restart. |
gateway.oidc.scopes | Your provider needs scopes beyond openid email profile | The code default applies. |
gateway.oidc.defaultTenantId | Single-tenant install | Tenant is resolved from the user's email domain instead. |
selfHost.bootstrap.tenantName | selfHost.bootstrap.enabled is true | The chart refuses to render. The Job does not guess a tenant name. |
selfHost.bootstrap.tenantId | You want to know the tenant id before the first run, to set gateway.oidc.defaultTenantId in the same release | The Job reuses a tenant with the same name, or makes one, and prints ALIGN_TENANT_ID=<uuid> in its log. |
selfHost.bootstrap.adminEmail | You want a first org_admin before anyone signs in | No admin is created. Promote someone by hand (Step 8, part 3). An existing account is never promoted by the Job. |
selfHost.bootstrap.starterRules.packs | You want no starter rules, or the healthcare pack | ["general"]: the four day-one rules are loaded for the first admin. Needs adminEmail and brain.enabled (the default). [] loads nothing. An unknown pack name makes the chart refuse to render. |
gateway.cognito.googleIdpName, gateway.cognito.azureIdpName | Your Cognito pool's provider names differ from Google and Microsoft | The code defaults apply. |
gateway.serviceAccount.name | You want a gateway-specific service account for cloud access | The shared serviceAccount is used. |
gateway.leads.notifyEmails | Never on a self-host: only used when gateway.leads.enabled is true, which only Align's hosted environments set | Nobody is emailed when a lead confirms; the lead is still stored. |
gateway.leads.registeredOffice | Never on a self-host (same gate as above) | The confirmation email footer uses the registered office built into the gateway. |
ui.analytics.enabled | Never on a self-host: only Align's hosted environments turn it on | false: the UI pod gets no PostHog key or host, loads no analytics script and sends nothing off-host. |
gateway.analytics.reviewerEmails | You give an outside reviewer an account and want their usage kept apart from your users' | Nobody is tagged reviewer. Only matters if the gateway sends product analytics, which needs a PostHog key. |
gateway.analytics.internalInstallIds | You run the CLI in local mode yourself and want those installs kept apart from your users' | No local CLI install is tagged internal. Only matters if the gateway sends product analytics. |
Connectors
| Value | Required when | If left empty |
|---|---|---|
connectors.align.defaultTenantId | Single-tenant install exposing the MCP endpoint | Not "no tenant". Every agent call resolves to the fixed id 00000000-0000-0000-0000-000000000001. |
connectors.align.oauth.resourceUrl | connectors.align.oauth.enabled is true | The chart refuses to render. Set it to the public MCP URL clients are given, with no trailing slash. It must match, character for character, the resource the gateway's authorization server is configured to issue tokens for. |
connectors.align.ingress.className | connectors.align.ingress.enabled is true | The Ingress has no class and no controller claims it. |
Licence service
Align runs this for its own customers. It is off (licence.enabled: false) and a self-host never turns it on. Its three Secret names have no default, and the chart refuses to render it without all three.
| Value | Required when | If left empty |
|---|---|---|
licence.secrets.database | licence.enabled is true | The chart refuses to render. The Secret holds database-url for the service's own database, never the gateway's. |
licence.secrets.signingKey | licence.enabled is true | The chart refuses to render. The Secret holds signing-private-key, the RSA key that signs the licences returned. |
licence.secrets.adminToken | licence.enabled is true | The chart refuses to render. The Secret holds admin-token, at least 32 characters. |
licence.ingress.host | licence.ingress.enabled is true | The chart refuses to render. No hostname is shipped; it is set with the DNS in align-infra. |
licence.image.tag | Never | Follows global.imageTag, which is what you want. |
Secrets you supply
With secrets.create: false, which is the production path, every row here is ignored and the
value belongs in the Secret named in section 1 instead.
The "Required when" column describes the development path, secrets.create: true.
| Value | Required when | If left empty |
|---|---|---|
secrets.internal.masterEncryptionKey | Always, through values or the Secret | The gateway refuses to boot. Nothing generates this one. |
secrets.internal.jwtSecret, secrets.internal.cookieSecret, secrets.internal.encryptionKey, secrets.internal.serviceAuthToken, secrets.internal.mcpAlignToken | Never, under secrets.create: true | Generated on first install, and read back from the existing Secret on upgrade so sessions and the connector service token survive. Under create: false nothing generates them and the Secret keys must already exist. |
secrets.internal.serviceAuthTokenSlack, secrets.internal.serviceAuthTokenTeams, secrets.internal.serviceAuthTokenGithub | Never, under secrets.create: true. Under create: false, each is required when you run that connector (the GitHub one when you use a GitHub App). | One token per relay connector. The gateway uses it to tell which connector relayed a person's conflict verdict, and hands that connector its tenant's credentials only when it presents its own token. Under create: true they are generated on first install and kept on upgrade. Without its key, the connector's credential reads get 403. See Relay connector tokens. |
secrets.internal.githubInboundToken | Never, under secrets.create: true. Under create: false, add the github-inbound-token key yourself whenever connector-github is enabled. | The token only the gateway holds for calls into connector-github's /internal/* routes. Both the gateway and connector-github require it (no optional), so without the key both pods stay in CreateContainerConfigError; an unset value refuses every internal call rather than falling back to serviceAuthToken. With External Secrets, add the property and set externalSecrets.githubInboundToken: true. |
secrets.internal.serviceAuthTokenAlign | Never, under secrets.create: true. Under create: false, add the service-auth-token-align key yourself if you run the hosted MCP server. | The hosted MCP server's own token, and the only service token the gateway accepts on POST /integrations/:key/enable. Without it the MCP server falls back to serviceAuthToken: every tool works except connect, which answers 403, and the gateway warns at startup. With External Secrets, add the property and set externalSecrets.alignRelayServiceToken: true. See Hosted MCP server token. |
secrets.internal.serviceAuthTokenJira, secrets.internal.serviceAuthTokenConfluence, secrets.internal.serviceAuthTokenZoom, secrets.internal.serviceAuthTokenLinear, secrets.internal.serviceAuthTokenNotion, secrets.internal.serviceAuthTokenAzureDevops, secrets.internal.serviceAuthTokenM365Documents, secrets.internal.serviceAuthTokenGoogleDrive | Never, under secrets.create: true. Under create: false they are optional. | One token per connector that used to call the gateway with serviceAuthToken, so the gateway can tell those connectors apart. If a key is absent that connector keeps working on serviceAuthToken, and the gateway warns at startup. With External Secrets, add all seven properties and set externalSecrets.connectorRelayServiceTokens: true. See Connector tokens. |
secrets.llm.existingSecret | You want the LLM keys to stay out of your values file | Empty: the chart renders its own <release>-llm Secret from the keys below, and the brain reads that. Set: the chart renders no LLM Secret, and the brain reads the Secret you name, with the same keys (openai-api-key, anthropic-api-key, azure-embedding-api-key, embedding-api-key). Setting it together with an inline key, or with externalSecrets.enabled, fails the render, so one Secret has one writer. See Keep the key out of your values file. |
brain.preferredProvider | You hold both an OpenAI and an Anthropic key and want to choose between them | Empty: the brain uses its own default, anthropic. Accepts openai or anthropic only; anything else fails the render. Do not also set PREFERRED_PROVIDER in brain.extraEnv: that fails the render too. |
secrets.llm.openaiApiKey, secrets.llm.anthropicApiKey | You use a hosted model | The brain has no model to call. Leave the OpenAI one empty if you run a local model, so the embeddings fallback cannot fire. |
secrets.llm.azureEmbeddingApiKey | embedding.profile is azure-openai-te3-small-384 | The Brain refuses to start, naming AZURE_OPENAI_EMBEDDING_API_KEY. Unused on any other profile. See Embeddings. |
secrets.llm.embeddingApiKey | embedding.profile is openai-compatible-384 and your server needs a bearer | Optional. The Brain sends a placeholder key, which most in-cluster servers ignore. |
secrets.tls.secretName, secrets.tls.commonName | Never | Under secrets.create: true and secrets.tls.create: true they default to align-local-tls and *.align.local, and a self-signed certificate IS generated. That is a local-development path. |
secrets.stripe.secretKey, secrets.stripe.webhookSecret, secrets.stripe.priceIdTeam | Never in a self-hosted install | Nothing. These are Align's hosted billing and have no effect here. |
Connector OAuth credentials
Required for the connector you enabled, and for nothing else. An enabled connector with no credentials starts and fails when a user tries to connect it, not at install, so an empty value here does not show up until someone clicks Connect. See OAuth Apps for how to create each app.
| Connector | Values |
|---|---|
| Slack | secrets.oauth.slack.clientId, secrets.oauth.slack.clientSecret, secrets.oauth.slack.signingSecret |
| Slack, per-user sign-in | secrets.oauth.slackPersonal.clientId, secrets.oauth.slackPersonal.clientSecret |
| GitHub, OAuth app | secrets.oauth.github.clientId, secrets.oauth.github.clientSecret |
| GitHub, App | secrets.oauth.github.appId, secrets.oauth.github.appPrivateKey, secrets.oauth.github.appClientId, secrets.oauth.github.appClientSecret |
| GitHub, per-user sign-in | secrets.oauth.github.personalClientId, secrets.oauth.github.personalClientSecret |
| Jira and Confluence | secrets.oauth.atlassian.clientId, secrets.oauth.atlassian.clientSecret (one Atlassian app covers both) |
| Linear | secrets.oauth.linear.clientId, secrets.oauth.linear.clientSecret |
| GitLab | secrets.oauth.gitlab.clientId, secrets.oauth.gitlab.clientSecret |
| Notion | secrets.oauth.notion.clientId, secrets.oauth.notion.clientSecret |
| Teams | secrets.oauth.teams.clientId, secrets.oauth.teams.clientSecret, secrets.oauth.teams.botAppId, secrets.oauth.teams.botAppPassword, secrets.oauth.teams.botAppTenantId |
| Azure DevOps | secrets.oauth.azureDevOps.clientId, secrets.oauth.azureDevOps.clientSecret, secrets.oauth.azureDevOps.tenantId (Entra ID app registration; no common fallback - an ADO organization is pinned to one tenant) |
| M365 Documents | secrets.oauth.m365Documents.clientId, secrets.oauth.m365Documents.clientSecret, secrets.oauth.m365Documents.tenantId (Entra ID app registration; SharePoint site pages are tenant-scoped, same reasoning as Azure DevOps above) |
| Google Drive (off by default, not usable yet) | secrets.oauth.googleDrive.clientId, secrets.oauth.googleDrive.clientSecret, secrets.oauth.googleDrive.webhookSecret (a Google OAuth client separate from the sign-in one; read-only drive.readonly scope; webhookSecret signs the per-tenant Drive channel tokens, and left empty every Drive webhook is rejected). Only read when connectors.googleDrive.enabled is true, which is false by default and does nothing useful until the gateway side exists (ALI-1575) |
| Google Meet (off by default, not usable yet) | secrets.oauth.googleMeet.clientId, secrets.oauth.googleMeet.clientSecret, secrets.oauth.googleMeet.webhookSecret (a Google OAuth client separate from the sign-in one; read-only meetings.space.readonly scope; webhookSecret signs the per-tenant Pub/Sub push token, and left empty every Meet webhook is rejected). Only read when connectors.googleMeet.enabled is true, which is false by default and does nothing useful until the gateway side exists (ALI-1658) |
| Google Meet events (off by default, not usable yet) | connectors.googleMeet.eventsTopic, connectors.googleMeet.eventsTenantId (one Pub/Sub topic for one tenant, the dogfood setup; eventsTopic is projects/<project>/topics/<topic> and eventsTenantId is the tenant UUID allowed to use it. Set both or neither: the chart refuses to render a topic without a tenant. Left empty, no Workspace Events subscription is created and the hourly renewal sweep does not start, so Meet never delivers events). Only read when connectors.googleMeet.enabled is true. Transcript polling needs neither of these: while connectors.googleMeet.enabled is true the gateway polls every 5 minutes for each tenant with a connected Meet account, from the moment they connect and never 30 days back. It has no chart value; set GOOGLE_MEET_TRANSCRIPT_POLL=disabled, GOOGLE_MEET_TRANSCRIPT_POLL_INTERVAL_MS (default 300000, minimum 60000) or GOOGLE_MEET_POLL_BACKFILL_HOURS (first poll only, up to 720) in gateway.extraEnv |
| Shared Entra app (Teams, Azure DevOps, SharePoint) | secrets.oauth.entra.clientId, secrets.oauth.entra.clientSecret, secrets.oauth.entra.tenantId (one registration for all three flows; a per-flow block above wins; ALI-1455) |
This table is not the whole credential surface, and the gap is worth knowing rather than
glossing. Several webhook and App credentials are read by the chart from Secret keys that
values.yaml declares no value for, so they can only be supplied by pre-creating the Secret: the
GitHub App's webhook-secret and app-installation-id (in align-oauth-github), Zoom's
client-id, client-secret and webhook-secret (in align-oauth-zoom), and the Teams
notification client-state (in align-teams-webhook). A webhook secret is a signature check, so
treat an unset one as a control you have not configured.
Teams, delegated-only install
connectors.teams.disableRealtimeTranscripts (default false). Set true if your Azure AD
admin granted only delegated permissions and not an Application permission for
Teams meeting transcripts - a pilot tenant is the usual case. The connector otherwise calls
Microsoft Graph with an app-only token at startup, for every mapped tenant, to keep a
tenant-wide real-time transcript subscription alive; without the Application permission that
call is rejected and logged as a failure on every start. With the switch on, the connector
skips that call entirely - no app-only token, no subscription, no startup failure - and
still serves Discover scans over your delegated OAuth scopes.
Cloud secret manager
| Value | Status |
|---|---|
externalSecrets.secretStore.name | Required when externalSecrets.enabled is true. |
externalSecrets.provider, externalSecrets.aws.region, externalSecrets.azure.vaultUrl, externalSecrets.azure.tenantId, externalSecrets.gcp.projectId, externalSecrets.vault.server | Read by no template. Provider selection comes from the SecretStore you point at, and externalSecrets.aws.secretPath is used as the remote key prefix for every provider. |
Telemetry export, tracing and GPU
| Value | Required when | If left empty |
|---|---|---|
telemetry.export.prometheus.pushGatewayUrl, telemetry.export.datadog.apiKeySecretName | Never | Read by no template. There is no telemetry export in the current chart; usage metrics stay in your database. For metrics scraping use podMonitor.enabled, and for traces use observability below. |
observability.grafanaCloud.otlpEndpoint | observability.grafanaCloud.enabled is true | The collector has no endpoint to export to. |
observability.grafanaCloud.authToken, observability.datadog.apiKey | Never | Read by no template. The collector reads the credential from a Secret named align-otel, and that reference is required, so enabling a backend without the Secret gives you a collector pod that will not start. |
gpu.llmServer.quantization, gpu.llmServer.maxModelLen | The model you chose needs them | vLLM's own defaults apply, which for a large model on a small GPU means it fails to load. |
Keeping this page true
A hand-written page describing 153 settings goes stale the first time someone adds one.
scripts/check-values-reference-parity.sh runs in the repository's Repo Guards job and fails
when a top-level key, or a value whose default is an empty string, appears in
charts/align/values.yaml and nowhere on this page. It also compares the two counts stated at
the top of this page against the chart. Deliberate omissions go in
scripts/config/values-reference-allowlist.txt with a date and a reason, and an entry that no
longer matches anything fails as drift, so the list cannot accumulate stale exemptions.
What that guard checks, stated so a green run is not read as more than it is: that every such
key is mentioned here, and that the two counts are right. It cannot check that what this
page says about a key is true, and it cannot see a setting the chart reads but values.yaml
never declares. Those parts are a read.