Skip to main content

Self-host requirements

Self-hosting Align needs more than Postgres and Redis. This page is the whole list, with what breaks if you leave each item out. Read it before you scope a pilot.

The list​

ItemNeeded?What happens without it
Kubernetes 1.25+, Helm 3.10+, an ingress controller, two DNS names, TLSYesThere is no non-Kubernetes install. The one-VM pilot installs a single-node k3s for you
PostgreSQL 15+ with the pgvector extensionYesMigrations create the extension, so a database that cannot load it fails at install
An LLM, of your choosingYesThe Brain has no model to extract decisions with. See the LLM
An OpenAI key, for embeddingsOnly for natural-language search on the default profileQuestions return 503 embedding_provider_unavailable. Keyword search still works. See Embeddings
Registry pull access, or the offline bundleYes, one of the twoThe images and chart are in a private registry. You give us an AWS account ID and we grant it pull access. Without an AWS account, ask for the offline bundle, which needs no registry access
ALIGN_MASTER_ENCRYPTION_KEY (32 bytes)YesThe gateway refuses to start
Sign-in through your identity provider, using OIDCYesOIDC is the documented self-host sign-in. See sign-in
SCIM provisioning from your identity providerNoWorks with the chart as it is. See SCIM provisioning
A Cognito placeholder secretOnly on releases that predate ALI-1755Older gateways refuse to start in production without it, even with OIDC on. Current main accepts a working OIDC config instead
Redis (any Redis-compatible server)Yes above one gateway replicaThe chart refuses to render. It is also the job queue when there is no SQS, so with one replica and no Redis the gateway refuses to start unless you set gateway.queue.provider: memory
A signed licence JWTYes, by contractThe software does not stop without one. See the licence
SQSNoThe job queue uses Redis when there is no SQS. See the job queue
An S3 bucketNoThe raw-event archive stays off unless you set S3_RAW_EVENTS_BUCKET

Where the answer is "it depends", the section below says on what.

The LLM​

Align reads your team's conversations and tickets and asks a model what was decided. You pick the model. You cannot skip it.

You runWhere the text goesNotes
OpenAI or Anthropic, with your own keyTo that providerSet secrets.llm.openaiApiKey or secrets.llm.anthropicApiKey. See LLM configuration
Azure OpenAI, in your tenantTo your Azure resourceThe request shape is pinned by a test
An OpenAI-compatible server (Ollama, vLLM) in your cluster or VPCNowhere outside your networkSet as the custom provider. You must allow the in-cluster address with global.customLlmEndpoint.allowlist, and global.customLlmEndpoint.allowHttp if it has no TLS. This holds only while no cloud API key is set. A configured key still gets Discover scans
The bundled vLLM server on your GPU nodesNowhere outside your networkgpu.llmServer.enabled. You bring the model image

Where the data goes. The Brain sends the text it is analysing, which is captured source text and decision text, to whichever model you configured. The model call goes from your cluster to that endpoint, not through Align Tech Ltd. The calls to us are the image pull and, unless you run an offline licence, a small daily licence check-in with four values (see Licensing). Connectors still call the SaaS tools they read.

Retention and zero-data-retention terms are your provider's. For a hosted provider, the retention you get is whatever your agreement with OpenAI, Anthropic or Microsoft says. If that answer does not satisfy your security team, run the model in your own network.

PII redaction is best effort. The Brain runs an entity-recognition pass plus a pattern pass over user prompts, message history and tool results before they go to the model. System prompts are not redacted unless the caller redacts them. It is on unless you set ENABLE_PII_REDACTION=false (the chart does not set it, so that goes in extraEnv). Embeddings are not covered. The default is read in services/brain/app/pii_redaction.py.

Embeddings are the one outbound call on a default install. The default profile embeds with OpenAI text-embedding-3-small, so natural-language search needs OPENAI_API_KEY and sends the text it embeds to OpenAI. Set embedding.profile to run them on your Azure OpenAI resource or an in-cluster server instead (see Embeddings). Those profiles leave conflict and supersession detection off until their thresholds are measured. Anthropic, a custom endpoint and the GPU server serve chat only. Keyword search and capture still work.

Sign-in​

A self-host signs people in through your identity provider with OIDC. Set gateway.oidc.enabled: true (it defaults to false) and create the align-oidc secret. A tenant must exist first, and the bootstrap Job creates the first admin. See Installation and OIDC authentication.

You do not need Cognito. The boot guard in services/gateway/src/auth/cognitoAuth.ts lets a production gateway start with a working OIDC config and no Cognito values (ALI-1755). A gateway from an older release crashes at boot without them. If yours logs Cognito configuration missing, create align-cognito with dummy values, as Installation describes. Ask us which release first carries the change.

The licence​

Every plan that self-hosts needs a signed licence JWT, and the contract says so. The software is looser than the contract today:

  • license.enabled defaults to false in the chart.
  • The gateway reads the licence at startup and logs whether it is valid. A missing or invalid licence does not stop it booting.
  • Seat enforcement is off by default (LICENSE_ENFORCEMENT_ENABLED) and the check is not attached to any route yet.

So the product does not lock without a licence today. You still need one. The file is validated on your machine, and a connected install also checks in daily. See Licensing.

Redis​

With one gateway pod, Redis is optional. It holds Discover job state and progress streaming.

With more than one, it is required, because session revocation lives in the gateway's cache. Without a shared store, a revoked session stays valid on every pod that did not serve the revoke call. Set gateway.redis.url or gateway.redis.secretName. The chart fails the render otherwise. gateway.allowUnsharedSessionStore=true overrides that, and you accept per-pod revocation if you use it. Details are in the overview.

The job queue​

Discover scans, bulk approvals and bulk deletes run on a queue. The gateway has three backends: Redis, SQS and in-memory. Only the first two survive a pod restart.

BackendSurvives a restartNeeds
RedisYes, if the Redis keeps its data (see below)A Redis the gateway can reach (gateway.redis.*, or the bundled one)
SQSYesThree queue URLs (gateway.sqs.*) and AWS access (IRSA on EKS)
In-memoryNoNothing. Queued work is lost when the pod stops

How the gateway picks one. gateway.queue.provider in the chart sets the gateway's QUEUE_PROVIDER. Left empty, which is the default, the gateway chooses in this order:

  1. SQS, if the queue URL for that queue is set.
  2. Redis, if the gateway has a Redis URL.
  3. Neither: in production the gateway refuses to start, and says why. It does not fall back to the in-memory queue on its own.

So a cluster with Redis and no SQS gets the Redis queue with no extra setting. A cluster with both keeps using SQS. Set gateway.queue.provider to redis or sqs to force one. The chart fails to render if the provider you name has no settings (redis with no Redis, sqs with no queue URL).

In-memory is opt-in. To run on it on purpose, for a pilot you can afford to lose, set gateway.queue.provider: memory. A pod restart then drops queued and running work with no retry. Outside production (local dev) the gateway uses it when nothing else is configured.

What the Redis queue guarantees. Delivery is at least once. A worker takes a message and has until the visibility timeout (the same value SQS uses for that queue) to acknowledge it. If the pod is killed mid-scan, the message returns to the queue after that timeout and another pod (or the restarted one) picks it up. A handler that must not repeat work has to be safe to run twice, which the import, approval and delete handlers already are because SQS behaves the same way. A message received five times without an acknowledgement is parked on a dead list in Redis instead of retrying forever. Nothing reads that list yet, so a parked message means a stuck scan you have to look at by hand. A handler that runs longer than the visibility timeout is delivered a second time while the first still runs, as on SQS. The queue's data is the Redis you configure, so it lasts as long as that Redis does: the bundled Redis has persistence off by default (redis.persistence.enabled), so turn it on, or use a managed Redis, if the Redis itself can restart. Keys are namespaced per Helm release and namespace, so environments that share one Redis do not share jobs.

If the queue is down at start. In production the gateway refuses to start with Redis, or with SQS when gateway.queue.provider: sqs is set, rather than quietly using memory. SQS chosen only by its URLs keeps the older behaviour of logging an error and using memory. Outside production it logs a warning and falls back.