> ## Documentation Index
> Fetch the complete documentation index at: https://docs.neuraltrust.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Feature flags reference

> Every toggle that changes what the NeuralTrust Platform Helm chart deploys — PostgreSQL, Redis, Kafka, ClickHouse, image registry, storage class, and secrets. Includes the exact values keys, required databases/users for external services, and the secrets you must pre-create per scenario.

This page is the single source of truth for the **feature flags** that change what the chart deploys. Use it when you need to decide between in-cluster and external infrastructure, mirror images, customize storage, or pre-create secrets.

For end-to-end install walkthroughs, see the [per-cloud guides](./overview#cloud-specific-guides). For the deployment-model decision (hybrid vs self-hosted), see [Deployment models](./deployment-models).

## Component on/off toggles

The chart's primary `enabled` flags decide which subcharts are rendered.

| Component               | Values key                                           | Default     | Notes                                                                                                                                            |
| ----------------------- | ---------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| Data Plane              | `neuraltrust-data-plane.dataPlane.enabled`           | `true`      | API + worker + Kafka Connect                                                                                                                     |
| Control Plane           | `neuraltrust-control-plane.controlPlane.enabled`     | **`false`** | Off = hybrid (CP on SaaS). On = self-hosted.                                                                                                     |
| TrustGate               | `trustgate.enabled`                                  | `true`      | admin + gateway + actions + Redis                                                                                                                |
| Firewall                | `neuraltrust-firewall.firewall.enabled`              | `true`      | gateway + 5 workers — **always on** in supported topologies; the choice is CPU vs GPU workers (see [Firewall deployment](./firewall#cpu-vs-gpu)) |
| SIEM Connectors         | `neuraltrust-siem-connectors.siemConnectors.enabled` | `false`     | Off — opt-in                                                                                                                                     |
| Watchdog (self-healing) | `neuraltrust-watchdog.enabled`                       | `false`     | Off — opt-in                                                                                                                                     |
| OpenTelemetry Collector | `global.observability.enabled`                       | `false`     | Off — opt-in                                                                                                                                     |

A zero-config `helm install` deploys Data Plane + TrustGate + Firewall + in-cluster infra, **without** Control Plane — i.e. a hybrid-shaped stack ready for SaaS enrollment.

## PostgreSQL — in-cluster or external

PostgreSQL stores:

* **Control Plane** schema (RBAC, integrations, dashboards) — only when CP is enabled
* **TrustGate** schema (gateways, routes, plugins) — when TrustGate is enabled
* Customer prompts and responses are **never** stored here — those go to ClickHouse.

### In-cluster (default)

| Values key                                                                              | Default                       |
| --------------------------------------------------------------------------------------- | ----------------------------- |
| `neuraltrust-control-plane.infrastructure.postgresql.deploy`                            | `true`                        |
| `neuraltrust-control-plane.controlPlane.components.postgresql.persistence.storageClass` | `""` (cluster default)        |
| `neuraltrust-control-plane.controlPlane.components.postgresql.secrets.user`             | `neuraltrust`                 |
| `neuraltrust-control-plane.controlPlane.components.postgresql.secrets.password`         | `""` → auto-generated 32-char |
| `neuraltrust-control-plane.controlPlane.components.postgresql.secrets.database`         | `neuraltrust`                 |
| `neuraltrust-control-plane.controlPlane.components.postgresql.secrets.host`             | `control-plane-postgresql`    |

The in-cluster Postgres deploys regardless of `controlPlane.enabled` — TrustGate uses the same instance even in hybrid mode.

### External (managed service)

```yaml theme={null}
neuraltrust-control-plane:
  infrastructure:
    postgresql:
      deploy: false
  controlPlane:
    components:
      postgresql:
        secrets:
          name: "postgresql-secrets"
          host: "<external-host>"
          port: "5432"
          user: "neuraltrust"
          password: "<password>"            # or pre-create the secret
          database: "neuraltrust"

trustgate:
  global:
    env:
      DATABASE_HOST: "<external-host>"
      DATABASE_PORT: "5432"
      DATABASE_USER: "trustgate"
      DATABASE_PASSWORD: "<password>"
      DATABASE_NAME: "trustgate"
      DATABASE_SSL_MODE: "require"          # "disable" only for in-cluster
```

### Required databases & users when external

Create these **before** running `helm upgrade`. Most managed services let you create the cluster admin then run the `CREATE DATABASE` / `CREATE USER` statements below.

| Database      | User          | Required when             | Permissions                                                                              |
| ------------- | ------------- | ------------------------- | ---------------------------------------------------------------------------------------- |
| `neuraltrust` | `neuraltrust` | Always                    | `ALL PRIVILEGES ON DATABASE neuraltrust` — Prisma migrations need DDL on `public` schema |
| `trustgate`   | `trustgate`   | `trustgate.enabled: true` | `ALL PRIVILEGES ON DATABASE trustgate`, schema DDL                                       |

Recommended SQL:

```sql theme={null}
-- as the cluster admin
CREATE USER neuraltrust WITH PASSWORD '<strong-password>';
CREATE DATABASE neuraltrust OWNER neuraltrust;
GRANT ALL PRIVILEGES ON DATABASE neuraltrust TO neuraltrust;

CREATE USER trustgate WITH PASSWORD '<strong-password>';
CREATE DATABASE trustgate OWNER trustgate;
GRANT ALL PRIVILEGES ON DATABASE trustgate TO trustgate;

-- connect to each DB and grant on public schema (Postgres 15+)
\c neuraltrust
GRANT ALL ON SCHEMA public TO neuraltrust;
\c trustgate
GRANT ALL ON SCHEMA public TO trustgate;
```

<Note>
  The chart's `trustgate-postgresql-init` Job can create the `trustgate` database/user automatically **if** the credentials in `postgresql-secrets` have `CREATE USER` and `CREATE DATABASE` rights on the external instance. Most managed Postgres services restrict these — pre-creating the DB/user is the safer path.
</Note>

### Cloud-managed Postgres notes

| Provider                                      | Notes                                                                                                                       |
| --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| AWS RDS / Aurora PostgreSQL                   | Set `DATABASE_SSL_MODE: require`. Use IRSA for password rotation via Secrets Manager if desired.                            |
| Azure Database for PostgreSQL Flexible Server | Set `DATABASE_SSL_MODE: require`. Use private endpoint for VNet-internal access.                                            |
| Cloud SQL for PostgreSQL                      | Use Private IP. Authorize the GKE subnet. Set `DATABASE_SSL_MODE: require`.                                                 |
| CloudNativePG (on-prem)                       | Run `CREATE DATABASE … OWNER` from a privileged session; chart-side init Job works if the bootstrap user has CREATE rights. |

## Redis — in-cluster or external

TrustGate uses Redis for caching gateways, plugins, and rate-limit state. Data Plane and Control Plane do **not** use Redis.

### In-cluster (default)

Redis ships bundled with the TrustGate subchart as `trustgate-redis` (single StatefulSet, 10 GiB PVC). No additional configuration needed.

| Values key                             | Default                   |
| -------------------------------------- | ------------------------- |
| `trustgate.redis.replica.replicaCount` | `1`                       |
| `trustgate.redis.image.tag`            | `7.2.0-v20`               |
| `trustgate.redis.bind`                 | `0.0.0.0 -::` (dual-bind) |

### External Redis

Point TrustGate at a managed Redis (ElastiCache, Memorystore, Azure Cache for Redis) by setting env vars:

```yaml theme={null}
trustgate:
  redis:
    enabled: false                          # config-only flag (see caveat below)
  global:
    env:
      REDIS_HOST: "<external-host>"
      REDIS_PORT: "6379"
      REDIS_PASSWORD: "<password>"          # required for managed Redis
      REDIS_DB: "0"
```

<Warning>
  **Current chart caveat**: setting `trustgate.redis.enabled: false` changes the TrustGate env vars to point external, but the in-cluster `trustgate-redis` StatefulSet **still deploys** (the template isn't guarded on the flag). The unused pod is small (\~500m / 1 GiB) but it does consume cluster resources. A fix is planned for an upcoming chart release; until then, expect the in-cluster Redis pod to exist even when you target an external one.
</Warning>

## Kafka — in-cluster or external

Kafka is the event backbone between TrustGate, Data Plane API, the Data Plane worker, Kafka Connect, and the Control Plane scheduler (audit events).

### In-cluster (default)

| Values key                    | Default |
| ----------------------------- | ------- |
| `infrastructure.kafka.deploy` | `true`  |
| `kafka.replicaCount`          | `1`     |
| `kafka.persistence.size`      | `10Gi`  |

In-cluster Kafka has no auth — ClusterIP-only and never exposed outside the namespace.

### External Kafka

```yaml theme={null}
infrastructure:
  kafka:
    deploy: false
    external:
      bootstrapServers: "kafka.example.com:9092"
      # 'brokers' (a list) is also accepted and joined with commas
```

This sets the `bootstrapServers` for components that read from `infrastructure.kafka.external.*`. **Per-component Kafka env vars must also be overridden** for components that use a hardcoded default (see below).

### Authentication for external Kafka

<Warning>
  The chart does **not** wire SASL/SCRAM/mTLS automatically. The `infrastructure.kafka.external.secretName` / `secretKey` keys exist in `values.yaml` but are **not consumed** by templates today. Use `extraEnv` on each Kafka consumer to inject the auth env vars yourself.
</Warning>

```yaml theme={null}
neuraltrust-data-plane:
  dataPlane:
    components:
      api:
        extraEnv:
          - name: KAFKA_BOOTSTRAP_SERVERS
            value: "kafka.example.com:9093"
          - name: KAFKA_SECURITY_PROTOCOL
            value: "SASL_SSL"
          - name: KAFKA_SASL_MECHANISM
            value: "SCRAM-SHA-512"
          - name: KAFKA_SASL_USERNAME
            valueFrom:
              secretKeyRef: { name: kafka-auth, key: username }
          - name: KAFKA_SASL_PASSWORD
            valueFrom:
              secretKeyRef: { name: kafka-auth, key: password }
      worker:
        extraEnv: *kafka-auth-env             # same as api.extraEnv
      kafka:
        connect:
          bootstrapServers: "kafka.example.com:9093"
```

Pre-create the `kafka-auth` Secret in your release namespace.

### Per-component Kafka overrides

| Component               | Hardcoded default                                      | Override path                                                                                     |
| ----------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------- |
| Data Plane API          | `KAFKA_BOOTSTRAP_SERVERS=kafka:9092`                   | `neuraltrust-data-plane.dataPlane.components.api.extraEnv`                                        |
| Data Plane Worker       | `KAFKA_BOOTSTRAP_SERVERS=kafka:9092`                   | `neuraltrust-data-plane.dataPlane.components.worker.extraEnv`                                     |
| Kafka Connect           | `bootstrapServers=kafka:9092`                          | `neuraltrust-data-plane.dataPlane.components.kafka.connect.bootstrapServers`                      |
| Control Plane Scheduler | `KAFKA_BROKERS=kafka:9092`, `KAFKA_TOPIC=audit_events` | `neuraltrust-control-plane.controlPlane.components.scheduler.config.kafkaBrokers` / `.kafkaTopic` |
| Control Plane App       | `KAFKA_HOST=kafka`, `KAFKA_PORT=9092`                  | `neuraltrust-control-plane.controlPlane.components.app.config.kafkaHost` / `.kafkaPort`           |

### Cloud-managed Kafka notes

| Provider                         | Notes                                                                                                                           |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| AWS MSK (IAM)                    | Use IRSA + `aws-msk-iam-sasl-signer-java` on consumers — currently requires custom image; chart does not bundle it.             |
| Confluent Cloud                  | SASL/PLAIN over TLS; inject `KAFKA_SECURITY_PROTOCOL=SASL_SSL` and `KAFKA_SASL_MECHANISM=PLAIN` plus API key/secret.            |
| Azure Event Hubs (Kafka surface) | SASL/PLAIN over TLS on port 9093; username `$ConnectionString`, password = Event Hubs connection string. Inject via `extraEnv`. |

## ClickHouse — in-cluster or external

ClickHouse is the analytics database for the Data Plane — prompts, responses, traces, evals, metrics. All customer telemetry lives here.

### In-cluster (default)

| Values key                         | Default                                                                     |
| ---------------------------------- | --------------------------------------------------------------------------- |
| `infrastructure.clickhouse.deploy` | `true`                                                                      |
| `clickhouse.auth.username`         | `neuraltrust`                                                               |
| `clickhouse.auth.password`         | `""` → auto-generated 32-char in Secret `clickhouse` / key `admin-password` |
| `clickhouse.auth.database`         | `neuraltrust`                                                               |
| `clickhouse.persistence.size`      | `50Gi`                                                                      |

### External ClickHouse

```yaml theme={null}
infrastructure:
  clickhouse:
    deploy: false
    external:
      host: "<external-host>"
      port: "8123"                          # HTTP port; "8443" for ClickHouse Cloud
      user: "neuraltrust"
      password: ""                          # use --set or pre-created secret
      database: "neuraltrust"
      secretName: "clickhouse"
      secretKey: "admin-password"

neuraltrust-data-plane:
  dataPlane:
    components:
      clickhouse:
        enabled: true                       # keep true to render clickhouse-secrets + migrations Job
        host: "<external-host>"
        port: "8123"
        user: "neuraltrust"
        database: "neuraltrust"
```

### Required ClickHouse Cloud caveat

<Warning>
  The Data Plane API runs an `init` container that connects to ClickHouse on **native port 9000** to apply migrations — this is hardcoded in the chart today. ClickHouse Cloud exposes native on **port 9440 (TLS)** instead, so the migrations init container fails out of the box against Cloud.

  Workarounds: (a) pre-provision the schema yourself and disable the init container, (b) run a port-translating proxy that exposes 9000→9440 inside your VPC, or (c) deploy ClickHouse in-cluster instead. A configurable native port is on the chart roadmap.
</Warning>

### Required databases & users when external

| Database      | User          | Permissions                                 |
| ------------- | ------------- | ------------------------------------------- |
| `neuraltrust` | `neuraltrust` | `GRANT ALL ON neuraltrust.* TO neuraltrust` |

The `clickhouse-migrations` initContainer applies the schema automatically the first time the Data Plane API starts.

## Image registry — NeuralTrust GCR or your internal mirror

By default, every NeuralTrust image is pulled from\
`europe-west1-docker.pkg.dev/neuraltrust-app-prod/nt-docker/<name>`.

### Default (NeuralTrust GCR)

Pre-create the `gcr-secret` image pull secret in your release namespace. The chart **does not** create this secret — you have to do it yourself with the JSON key NeuralTrust provides:

```bash theme={null}
kubectl create secret docker-registry gcr-secret \
  --docker-server=europe-west1-docker.pkg.dev \
  --docker-username=_json_key \
  --docker-password="$(cat path/to/gcr-keys.json)" \
  --docker-email=admin@neuraltrust.ai \
  -n neuraltrust
```

The chart references this secret from every subchart's `imagePullSecrets` (defaults: `["gcr-secret"]`).

### Internal mirror (one override)

```yaml theme={null}
global:
  imageRegistry: "registry.internal.example.com/neuraltrust"
```

The chart's image helper strips the default GCP prefix and prepends yours — so\
`europe-west1-docker.pkg.dev/neuraltrust-app-prod/nt-docker/control-plane-api`\
becomes\
`registry.internal.example.com/neuraltrust/control-plane-api`\
with no other override required.

### Three escalating customization levels

| You mirror images with…                        | What to override                                                        |
| ---------------------------------------------- | ----------------------------------------------------------------------- |
| Same short names, same tags                    | only `global.imageRegistry`                                             |
| Same short names, custom tags                  | `global.imageRegistry` + per-component `image.tag`                      |
| Renamed paths (e.g. `my-registry.corp/cp-api`) | `global.imageRegistry` + per-component `image.repository` + `image.tag` |

Per-component override paths:

| Path                                                                                                      | Component     |
| --------------------------------------------------------------------------------------------------------- | ------------- |
| `neuraltrust-control-plane.controlPlane.components.{api,app,scheduler,postgresql}.image.{repository,tag}` | Control Plane |
| `neuraltrust-data-plane.dataPlane.components.{api,worker}.image.{repository,tag}`                         | Data Plane    |
| `neuraltrust-data-plane.dataPlane.components.kafka.connect.image.{repository,tag}`                        | Kafka Connect |
| `neuraltrust-firewall.firewall.gateway.image.*` / `workerDefaults.image.*`                                | Firewall      |
| `trustgate.global.image.{image,tag}`                                                                      | TrustGate     |
| `trustgate.redis.image.{repository,tag}`                                                                  | Redis         |
| `clickhouse.image.{repository,tag}`                                                                       | ClickHouse    |
| `kafka.image.{repository,tag}`                                                                            | Kafka         |

### Mirroring for air-gapped installs

```bash theme={null}
helm template neuraltrust-platform \
  oci://europe-west1-docker.pkg.dev/neuraltrust-app-prod/helm-charts/neuraltrust-platform \
  --version <VERSION> \
  -f my-values.yaml \
  | yq '.. | select(has("image")) | .image' \
  | sort -u
```

Mirror each image to your internal registry with `crane`, `skopeo`, or `docker pull/push`, then set `global.imageRegistry`. Verify rendered images:

```bash theme={null}
helm template . -f my-values.yaml | grep -E '^\s+image:' | sort -u
```

## Storage class — cluster default or explicit

PVCs are created for ClickHouse, Kafka, PostgreSQL, Redis, and the optional OpenTelemetry collector buffer.

### Cluster default (default behavior)

| Values key            | Default |
| --------------------- | ------- |
| `global.storageClass` | `""`    |

When empty, the chart **omits** `storageClassName` from PVC specs, which falls back to the cluster's default `StorageClass`. Confirm one exists:

```bash theme={null}
kubectl get storageclass
# default class is marked with (default)
```

### Explicit, cluster-wide

```yaml theme={null}
global:
  storageClass: "pd-balanced"               # GKE example
  # storageClass: "gp3"                      # AWS EKS
  # storageClass: "managed-csi-premium"      # Azure AKS
  # storageClass: "longhorn"                 # bare metal
```

### Per-component overrides

| Component                        | Values key                                                                              | Recommended for prod                                                    |
| -------------------------------- | --------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| ClickHouse                       | `clickhouse.persistence.storageClass`                                                   | SSD-backed (`pd-ssd`, `io2`, `managed-csi-premium`) for high-throughput |
| Kafka                            | `kafka.persistence.storageClass`                                                        | SSD-backed                                                              |
| PostgreSQL                       | `neuraltrust-control-plane.controlPlane.components.postgresql.persistence.storageClass` | SSD-backed                                                              |
| Redis (TrustGate)                | `trustgate.global.storageClass` (inherits umbrella `global.storageClass`)               | Default                                                                 |
| OTel collector buffer (optional) | `global.observability.collector.bufferPVC.storageClass`                                 | Default                                                                 |

Per-component override wins over `global.storageClass`. Empty per-component = inherit `global.storageClass` = inherit cluster default.

## Secrets — auto-generated, explicit, or pre-created

Two top-level flags control how the chart manages secrets.

| Values key                       | Default | Behavior                                                                                                |
| -------------------------------- | ------- | ------------------------------------------------------------------------------------------------------- |
| `global.autoGenerateSecrets`     | `true`  | Chart generates random secrets on first install; reuses existing on upgrade.                            |
| `global.preserveExistingSecrets` | `false` | When `true`, **all** secret templates are skipped (parent + subcharts). You must pre-create everything. |

Use `autoGenerateSecrets: true` for quick installs and dev/test. Use `preserveExistingSecrets: true` when you manage secrets with Vault, Sealed Secrets, External Secrets Operator, SOPS, or any other secret manager.

### Required secrets per scenario

Always required, regardless of mode:

| Secret       | Type              | Created by                    |
| ------------ | ----------------- | ----------------------------- |
| `gcr-secret` | `docker-registry` | **You** (manual, pre-install) |

Auto-generated by default (when `autoGenerateSecrets: true`):

| Scenario                                  | Secret                                                                      | Keys                                                                                                                                                                                                               |
| ----------------------------------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Hybrid** *(CP off)*                     | `data-plane-jwt-secret`                                                     | `DATA_PLANE_JWT_SECRET`                                                                                                                                                                                            |
|                                           | `trustgate-secrets`                                                         | `DATABASE_HOST`, `DATABASE_PORT`, `DATABASE_USER`, `DATABASE_PASSWORD`, `DATABASE_NAME`, `DATABASE_SSL_MODE`, `DATABASE_URL`, `SERVER_SECRET_KEY`, `NEURAL_TRUST_FIREWALL_URL`, `NEURAL_TRUST_FIREWALL_SECRET_KEY` |
|                                           | `firewall-secrets`                                                          | `JWT_SECRET` *(optional `HUGGINGFACE_TOKEN`)*                                                                                                                                                                      |
|                                           | `postgresql-secrets`                                                        | `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB`, `POSTGRES_HOST`, `POSTGRES_PORT`, `DATABASE_URL`, `POSTGRES_PRISMA_URL`                                                                                       |
|                                           | `clickhouse`                                                                | `admin-password`                                                                                                                                                                                                   |
|                                           | `clickhouse-secrets`                                                        | `CLICKHOUSE_HOST`, `CLICKHOUSE_PORT`, `CLICKHOUSE_USER`, `CLICKHOUSE_DATABASE`                                                                                                                                     |
|                                           | `neuraltrust-ingress-tls` *(if `global.ingress.tls.autoGenerate: true`)*    | `tls.crt`, `tls.key`                                                                                                                                                                                               |
| **Self-hosted** *(adds CP)*               | `control-plane-secrets`                                                     | `CONTROL_PLANE_JWT_SECRET`, `TRUSTGATE_JWT_SECRET`, `FIREWALL_API_URL` *(optional Resend / LLM keys)*                                                                                                              |
| **Optional** *(only if non-empty values)* | `openai-secrets`, `google-secrets`, `resend-secrets`, `huggingface-secrets` | provider-specific                                                                                                                                                                                                  |

### Pre-creating secrets (`preserveExistingSecrets: true`)

Required for: production setups with external secret managers, GitOps flows where chart-rendered Secrets cause drift, or air-gapped environments where you bake secrets into your golden state.

```yaml theme={null}
global:
  preserveExistingSecrets: true             # chart will not render any secret templates
```

You must pre-create the secrets above before `helm upgrade --install`. The chart ships [`create-secrets.sh`](https://github.com/NeuralTrust/neuraltrust-platform/blob/main/create-secrets.sh) and [`SECRETS.md`](https://github.com/NeuralTrust/neuraltrust-platform/blob/main/SECRETS.md) as a starting template.

For per-secret keys and a worked example, see [Secrets management](./secrets).

## Topology cheat sheet

A condensed view of the toggles people set on day one:

```yaml theme={null}
global:
  platform: "gcp"                  # aws | gcp | azure | openshift | kubernetes
  domain: "platform.example.com"
  storageClass: ""                 # "" = cluster default
  imageRegistry: ""                # "" = NeuralTrust GCR
  autoGenerateSecrets: true        # false + preserveExistingSecrets for prod
  preserveExistingSecrets: false

neuraltrust-control-plane:
  controlPlane:
    enabled: false                 # true = self-hosted
  infrastructure:
    postgresql:
      deploy: true                 # false = external

neuraltrust-data-plane:
  dataPlane:
    enabled: true

trustgate:
  enabled: true
  redis:
    enabled: true                  # false + REDIS_HOST env = external (see Redis caveat)

neuraltrust-firewall:
  firewall:
    enabled: true                  # always on in supported topologies (CPU or GPU workers)

infrastructure:
  clickhouse:
    deploy: true                   # false = external
  kafka:
    deploy: true                   # false = external
```

## Related guides

* [Deployment models](./deployment-models) — hybrid vs self-hosted comparison
* [Configuration scenarios](./configuration) — side-by-side values files for common topologies
* [Secrets management](./secrets) — pre-creation script, External Secrets Operator
* [Image catalog](./images) — every image deployed in each model
* [Firewall deployment](./firewall) — CPU vs GPU workers, TrustGate integration
