Self-hosted runs every plane in your environment: the console, the product control planes, the data planes and the analytics stack. Nothing depends on NeuralTrust SaaS. The one connection that leaves your network by default is TrustGuard’s scoring call to the inference plane, and you can bring that in-cluster too.
The chart calls this mode External (global.deploymentMode: external), and
the values, file names and log lines on this page use that name.
Choose Self-hosted for strict data-residency mandates, air-gapped environments,
or when policy forbids any outbound dependency on NeuralTrust SaaS. There are no
console-issued tokens: the platform is its own configuration source. Decide one
thing up front — where the ML detectors run:
Before you start
- The variant: hosted inference plane or air-gapped
- The firewall key from NeuralTrust, for the region you want scoring in — hosted variant only; ask before you start
- A cluster with an ingress controller and Helm 3.8+, sized per Prerequisites
- The console hostname, decided before install — see Decide the console hostname first
- PostgreSQL, Redis and ClickHouse — see Datastores
- The registry key, or a mirror of the images for an air-gapped cluster
- An SMTP or email provider, for console invitations and password resets
Architecture
- Configuration comes from PostgreSQL, not from a config-sync stream. Each product’s data plane reads its configuration directly from that product’s database, which its own in-cluster control plane owns and migrates.
- DataCore replaces DataAgent. There is no SaaS pushing queries over an
outbound stream, so an in-cluster HTTP API serves tenant-scoped queries from
ClickHouse instead, plus deployment metadata from its own
datacoredatabase. - The Firewall is NeuralTrust-hosted by default. TrustGuard sends the text
its ML detectors score to the NeuralTrust inference plane and gets a score
back; nothing is stored there. Ask NeuralTrust for the install’s firewall key
before you deploy, or run the Firewall in your cluster with
global.firewall.deploy: true— see Firewall.
What runs
A complete platform install. Every table below is deployed by the same umbrella chart in one release.Request path
Control plane
Analytics
TrustGuard only
Turn TrustGate off to run TrustGuard on its own:values-external-trustguard-only.yaml.example. It
removes TrustGate’s workloads, hosts and Secrets, plus what only TrustGate uses:
the in-cluster Firewall’s complexity worker, MCP OAuth and the Admin API
machine credentials.
Leave dataPlane on: the console reads runtime activity from the data-plane
API. The audit trail does not depend on it; audit events are sent as OTLP logs
and shown under Telemetry → Logs.
Prerequisites
- A Kubernetes cluster with an ingress controller, and Helm 3.8+
- 3 nodes of 8 vCPU / 16 GiB with the NeuralTrust-hosted Firewall, or 4 of 8 vCPU / 32 GiB with the Firewall in your cluster — the console and analytics stack on top of the data path — as the minimum of an autoscaled node pool; GPU options are in Capacity
- The firewall key NeuralTrust issues for this install, unless you run the Firewall in your cluster — ask for it before you start
- A base domain you can point at the cluster ingress
- PostgreSQL, Redis, and ClickHouse — see Datastores
- The registry key NeuralTrust sends you, or a mirror of the images for an air-gapped cluster — Container images
- An SMTP or email provider, for console invitations and password resets
Decide the console hostname first
APP_URL and NEXTAUTH_URL drive email links, SSO redirects, and OAuth
callbacks. Changing them later invalidates in-flight invitations and breaks
configured SSO redirect URIs, so pick the hostname before installing.
One limitation to know about: a handful of console values are NEXT_PUBLIC_*,
which Next.js inlines into the JavaScript bundle at image build time. Helm
cannot set them. In practice two matter, and both are cosmetic rather than
blocking — the SCIM tenant URL shown to administrators and the meta.location
returned to your IdP will show app.neuraltrust.ai. Enter your own host in your
IdP rather than copying what the screen displays.
Network
Self-hosted’s only outbound connection to NeuralTrust is the hosted Firewall:firewall.neuraltrust.ai (EU) or firewall.us.neuraltrust.ai (US) on 443, or
the same host over Google Cloud Private Service Connect. With the Firewall in
your cluster (global.firewall.deploy: true) a self-hosted install needs no outbound
connectivity to NeuralTrust at all. There is no inbound source IP to admit.
What you do need reachable:
global.firewall.deploy: true, mirror the images into an internal registry and
set global.imageRegistry with global.imageRegistryScope: all, so the
collector and Firewall images follow — see
Mirror to your own registry.
Datastores
Recommended for production: managed PostgreSQL and Redis outside the cluster, with ClickHouse in-cluster. The chart defaults to in-cluster PostgreSQL and Redis (deploy: true) so a proof of concept can helm install
without provisioning anything first — that is convenience, not the production
shape.
host, port or
sslMode (and Redis host / port / username / tls) inherits from
global.postgresql / global.redis before falling back to the in-cluster
Service names. Declare a managed datastore once on the global blocks; add a
per-service database: / redis: block only to send one service somewhere else.
In-cluster PostgreSQL (proof of concept)
Leaveglobal.postgresql.deploy: true. A
control-plane-postgresql-bootstrap Job creates each service role and database
using the password already in that service’s Secret, so you run no SQL by hand.
Expect a few restarts on the Postgres-backed workloads while the Job waits for
PostgreSQL, then they become Ready.
Managed PostgreSQL and Redis (production)
Name the endpoints once, disable the in-cluster stores you are replacing, and leave ClickHouse alone:trustguard database needs the pgcrypto extension: TrustGuard’s
migrations create it and fail without it. The control-plane database uses it
when it is available. The owner of a database may create it, since pgcrypto is
a trusted extension. On Azure Database for PostgreSQL, first allow-list it in the
azure.extensions server parameter.
Let the chart create the roles
WithmanagedBootstrap on, the chart runs a Job after install and after every
upgrade that creates any missing role and database in the table above, using
the passwords the services read, and creates pgcrypto in each database. Roles
and databases that already exist are kept, except that each role’s password is
set to the one in its service’s Secret, so the two cannot drift and a password
rotated in the Secret reaches the database on the next upgrade. Re-running it
is safe, so leave it on. Application tables stay with each service’s own
migrations.
It logs in with an administrator you name in a Secret. That login needs
CREATEROLE and CREATEDB, not superuser:
authMode: iam the roles are principals your cloud tooling creates, and the
chart refuses to render with both set. The Job is a Helm post-install and
post-upgrade hook, which Argo CD runs as a PostSync hook. Until it finishes,
the services restart while they wait for their databases.
No datastore password belongs in the values file. Point each service at a Secret
you created, including the control-plane role through
global.postgresql.passwordSecret — see
External per-service datastore credentials.
For minimum sizes see
Managed stores and
values-managed-datastores.yaml.example,
which ships with the chart. For RDS, Entra ID, Cloud SQL or AlloyDB IAM
authentication instead of passwords, see
IAM database authentication.
Running ClickHouse outside the cluster is supported but uncommon. Only if your
policy requires it, add:
Install
Self-hosted needs no console setup, no config-sync tokens, and no DataAgent enrollment. The platform is its own configuration source. The one credential you get from NeuralTrust is the firewall key, unless the Firewall runs in your cluster.Create the namespace and image pull secret
Create the bootstrap administrator Secret
DEPLOYMENT_MODE=external and both values are
non-empty. On password login the console checks these before normal user
authentication, and on a match upserts the user with a verified email and grants
OWNER on every team.Treat it as a break-glass account: it bypasses the normal user table by design.
Create a personal administrator through the UI afterwards.Create the firewall key Secret
FIREWALL_JWT: a per-install client that carries
the Firewall URL and can be revoked on its own. The URL decides the region your
text is scored in — firewall.neuraltrust.ai (EU) or
firewall.us.neuraltrust.ai (US) — so say which one you want when you ask; see
Inference plane.global.firewall.deploy: true in the next one.Write your values file
values-external.yaml.example. Copy it and set your platform and
domain:onprem-superadmin and trustguard-firewall. For the production shape,
start from values-managed-datastores.yaml.example and follow
Managed PostgreSQL and Redis.Install, and watch the ordering
- A pre-install Helm hook Job generates the MCP OAuth RSA signing key into a Secret. It checks for an existing key first, so upgrades do not rotate it and invalidate live access tokens. It only ever writes its own Secret — the Role is name-scoped.
- An init container runs
prisma migrate deployfollowed by the seed.
maxUnavailable: 0, so a failed migration
blocks the rollout rather than serving against a half-migrated schema.
control-plane-api reads the same schema and runs no migrations of its own —
it assumes the console has already applied them.The chart registry is public; only the container images are private. Always
pass --version so the release is reproducible.Verify
agentgateway-proxy,
agentgateway-mcp, trustguard-data-plane, data-plane-api, plus firewall
and its workers with global.firewall.deploy: true), the in-cluster control plane (agentgateway-admin,
trustguard-control-plane, control-plane-api, control-plane-app), and the
analytics stack (clickstack-collector, clickhouse, datacore,
alertengine-api, alertengine-worker). No dataagent pod appears.
In-cluster control-plane-postgresql and redis appear only while
deploy: true.
Data-plane readiness has no snapshot gate here, so Running and Ready track
each other closely. Point DNS at the ingress address, sign in with the
super-admin credentials, and create the organization.
Configuration comes from PostgreSQL
Config sync is off by default in External mode, and each product’s data plane reads configuration from its own database. Practical consequences:- There is no config-sync token to create and no snapshot cache to reason about
- Each product needs its own database and role, not one shared credential
kubernetes.io/tls Secret including ca.crt through
configSync.grpcTls.existingSecret; setting autoGenerate: false without one is
rejected at render time.
DataCore is reached by the console at DATACORE_URL with a JWT signed by a
secret that must equal DataCore’s AUTH_JWT_HS256_SECRET. The chart wires both
ends from a single generated value. If you override one by hand you must
override the other, or every dashboard returns an authorization error.
The checks above confirm the install came up. Before handing it over, work
through the validation checklist — it
covers what kubectl cannot see: DNS and TLS from the caller’s network, MCP
tested separately from the LLM route, policy decisions, backup ownership, and a
rehearsed restore.
Telemetry
Product telemetry stays in your in-cluster ClickHouse. To run with no outbound NeuralTrust telemetry — the air-gapped default — disable hosted export:global.observability.operationalTelemetry: true, which is off by default.
In a self-hosted install, both stay in your environment. See
Operational telemetry.
Public routing
AgentGateway exposes three surfaces in External mode: admin, proxy, and MCP.global.domain combines with default prefixes to render hostnames, and
the chart can auto-add wildcard hosts (*.llm.<domain> / *.mcp.<domain>) for
slug-based discovery.
What you have taken on
Worth stating plainly, since it is the real cost of running Self-hosted:- Database migrations are yours to run and to roll back
- Backups of PostgreSQL and ClickHouse are yours
- Certificate rotation for anything you supplied yourself
- The bootstrap credential is a standing break-glass path into every team
- Email deliverability — invitations and password resets stop working silently if it breaks
Known rough edges
Self-hosted is newer than Hybrid, and these are the issues you are most likely to meet. Each has a workaround in Troubleshooting.- Login lockout after repeated failed password attempts
- SCIM setup screens display the SaaS host instead of yours
- Alert evaluation does not currently run
- An in-cluster Firewall’s complexity state silently disables itself without a Redis URL
- Trace export returns 404 when the OTLP endpoint includes a signal path
High availability
The availability ladder is the same for every self-hosted model, so it lives on one page: what each tier survives, the replica and disruption settings the chart does and does not expose, the failure table, and the promotion runbook.High availability
Configuration lives in PostgreSQL
Self-hosted keeps its entire configuration state in the database rather than pulling it from anywhere, so a second cluster is already looking at the same policies the moment it starts. Nothing pulls configuration from NeuralTrust (the hosted Firewall, if you use it, is stateless and both clusters call it alike), and there is no last-known-good cache to worry about — the trade is that the database is now the thing that must not be lost.ClickHouse does not follow a promotion
Console sessions live in the region that issued them
A promotion signs operators out. They sign in again against the same PostgreSQL and everything is there — but plan for it in the runbook rather than discovering it mid-incident.Upgrades and migrations with two clusters
Both consoles runprisma migrate deploy in an init container against the same
database. Upgrade one cluster at a time, active first, and let its rollout
complete before starting the second. A migration is applied once and the second
cluster’s init container no-ops, but running both simultaneously against one
schema is worth avoiding — and the console rolls with maxUnavailable: 0, so a
failed migration blocks that cluster rather than serving a half-migrated schema.