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
| Item | Needed? | What happens without it |
|---|---|---|
| Kubernetes 1.25+, Helm 3.10+, an ingress controller, two DNS names, TLS | Yes | There is no non-Kubernetes install. The one-VM pilot installs a single-node k3s for you |
| PostgreSQL 15+ with the pgvector extension | Yes | Migrations create the extension, so a database that cannot load it fails at install |
| An LLM, of your choosing | Yes | The Brain has no model to extract decisions with. See the LLM |
| An OpenAI key, for embeddings | Only for natural-language search on the default profile | Questions return 503 embedding_provider_unavailable. Keyword search still works. See Embeddings |
| Registry pull access, or the offline bundle | Yes, one of the two | The 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) | Yes | The gateway refuses to start |
| Sign-in through your identity provider, using OIDC | Yes | OIDC is the documented self-host sign-in. See sign-in |
| SCIM provisioning from your identity provider | No | Works with the chart as it is. See SCIM provisioning |
| A Cognito placeholder secret | Only on releases that predate ALI-1755 | Older 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 replica | The 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 JWT | Yes, by contract | The software does not stop without one. See the licence |
| SQS | No | The job queue uses Redis when there is no SQS. See the job queue |
| An S3 bucket | No | The 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 run | Where the text goes | Notes |
|---|---|---|
| OpenAI or Anthropic, with your own key | To that provider | Set secrets.llm.openaiApiKey or secrets.llm.anthropicApiKey. See LLM configuration |
| Azure OpenAI, in your tenant | To your Azure resource | The request shape is pinned by a test |
| An OpenAI-compatible server (Ollama, vLLM) in your cluster or VPC | Nowhere outside your network | Set 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 nodes | Nowhere outside your network | gpu.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.enableddefaults tofalsein 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.
| Backend | Survives a restart | Needs |
|---|---|---|
| Redis | Yes, if the Redis keeps its data (see below) | A Redis the gateway can reach (gateway.redis.*, or the bundled one) |
| SQS | Yes | Three queue URLs (gateway.sqs.*) and AWS access (IRSA on EKS) |
| In-memory | No | Nothing. 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:
- SQS, if the queue URL for that queue is set.
- Redis, if the gateway has a Redis URL.
- 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.
Related
- Infrastructure requirements: what to provision first
- Installation: the step-by-step install
- LLM configuration: provider setup and the embeddings caveat
- Secrets: every secret the chart reads