The NeuralTrust Platform Helm chart can manage every required secret for you, or stay completely out of the way and let your existing secret management own the lifecycle. Pick the mode that fits your environment.
| Mode | Flag(s) | Behavior | Best for |
|---|
| Auto-generated (default) | global.autoGenerateSecrets: true | Helm creates and preserves secrets | Dev, staging, quick starts |
| Explicit values | global.autoGenerateSecrets: true + values set | Your values override auto-generation | Controlled environments |
| Pre-generated | global.preserveExistingSecrets: true | Helm never touches secrets | Production with Vault, Sealed Secrets, or External Secrets Operator |
Auto-generated secrets (default)
No action required. Deploy and all platform secrets are created automatically:
helm upgrade --install neuraltrust-platform \
oci://europe-west1-docker.pkg.dev/neuraltrust-app-prod/helm-charts/neuraltrust-platform \
--version <VERSION> \
--namespace neuraltrust --create-namespace
| Secret | Kubernetes Secret | Key |
|---|
| TrustGate server key | trustgate-secrets | SERVER_SECRET_KEY |
| TrustGate DB password | trustgate-secrets | DATABASE_PASSWORD |
| Control Plane JWT | control-plane-secrets | CONTROL_PLANE_JWT_SECRET |
| TrustGate JWT (synced) | control-plane-secrets | TRUSTGATE_JWT_SECRET |
| Data Plane JWT | data-plane-jwt-secret | DATA_PLANE_JWT_SECRET |
| PostgreSQL password | postgresql-secrets | POSTGRES_PASSWORD |
| ClickHouse admin password | clickhouse | admin-password |
SERVER_SECRET_KEY (TrustGate) and TRUSTGATE_JWT_SECRET (Control Plane) are always synchronized to the same value automatically. You can set either one explicitly and the other will follow.
How it works
- On first install, random 64-character alphanumeric values are generated for every required secret.
- On
helm upgrade, existing values are read from the cluster via Helm lookup and reused — secrets are never overwritten.
- Explicit (non-empty) values in your values file always take priority and become the source of truth.
Overriding a specific secret
trustgate:
global:
env:
SERVER_SECRET_KEY: "my-explicit-key" # overrides auto-generation
Pre-generated secrets
For environments where Helm must not create or update secrets — e.g. Vault, Sealed Secrets, or External Secrets Operator owns the lifecycle:
global:
autoGenerateSecrets: false
preserveExistingSecrets: true # Helm will NOT create or update secrets
When this flag is set, all required secrets must already exist in the namespace before deployment. Missing secrets cause workloads to fail.
Required secrets when preserveExistingSecrets: true
| Kubernetes Secret | Required keys |
|---|
clickhouse | admin-password |
postgresql-secrets | POSTGRES_HOST, POSTGRES_PORT, POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB, DATABASE_URL, POSTGRES_PRISMA_URL |
control-plane-secrets | CONTROL_PLANE_JWT_SECRET, TRUSTGATE_JWT_SECRET (must equal TrustGate SERVER_SECRET_KEY) |
data-plane-jwt-secret | DATA_PLANE_JWT_SECRET |
trustgate-secrets | SERVER_SECRET_KEY, DATABASE_PASSWORD (and NEURAL_TRUST_FIREWALL_URL, NEURAL_TRUST_FIREWALL_SECRET_KEY if using the Firewall) |
gcr-secret | docker-registry type — for pulling NeuralTrust images |
Optional (only if used): openai-secrets, google-secrets, resend-secrets, huggingface-secrets, firewall-secrets.
With preserveExistingSecrets: true, you are responsible for keeping SERVER_SECRET_KEY (in trustgate-secrets) and TRUSTGATE_JWT_SECRET (in control-plane-secrets) identical. The chart no longer enforces this for you.
Full secret reference
Data Plane
| Kubernetes Secret | Key | Required | Description |
|---|
data-plane-jwt-secret | DATA_PLANE_JWT_SECRET | Auto-generated | JWT for Data Plane API auth |
openai-secrets | OPENAI_API_KEY | No | OpenAI API key |
google-secrets | GOOGLE_API_KEY | No | Google API key |
resend-secrets | RESEND_API_KEY | No | Resend email API key |
huggingface-secrets | HUGGINGFACE_TOKEN | No | Hugging Face model access |
Control Plane
| Kubernetes Secret | Key | Required | Description |
|---|
control-plane-secrets | CONTROL_PLANE_JWT_SECRET | Auto-generated | JWT for Control Plane API auth |
control-plane-secrets | TRUSTGATE_JWT_SECRET | Auto-generated | Synced with SERVER_SECRET_KEY |
control-plane-secrets | FIREWALL_JWT_SECRET | If Firewall enabled | JWT shared with the firewall |
control-plane-secrets | MODEL_SCANNER_SECRET | No | Model scanner service secret |
control-plane-secrets | OPENAI_API_KEY | No | OpenAI API key |
control-plane-secrets | resend-api-key | No | Resend API key |
control-plane-secrets | resend-alert-sender | No | Alert notification email |
control-plane-secrets | resend-invite-sender | No | Invitation email |
PostgreSQL
| Kubernetes Secret | Key | Required | Description |
|---|
postgresql-secrets | POSTGRES_HOST | If pre-generating | Database hostname |
postgresql-secrets | POSTGRES_PORT | If pre-generating | Database port |
postgresql-secrets | POSTGRES_USER | If pre-generating | Database username |
postgresql-secrets | POSTGRES_PASSWORD | Auto-generated | Database password |
postgresql-secrets | POSTGRES_DB | If pre-generating | Database name |
postgresql-secrets | DATABASE_URL | If pre-generating | Connection URL (URL-encoded) |
postgresql-secrets | POSTGRES_PRISMA_URL | If pre-generating | Prisma-compatible URL |
TrustGate
| Kubernetes Secret | Key | Required | Description |
|---|
trustgate-secrets | SERVER_SECRET_KEY | Auto-generated | Server secret key (= TRUSTGATE_JWT_SECRET) |
trustgate-secrets | DATABASE_HOST | External DB | PostgreSQL host |
trustgate-secrets | DATABASE_PORT | External DB | PostgreSQL port |
trustgate-secrets | DATABASE_USER | External DB | PostgreSQL user |
trustgate-secrets | DATABASE_PASSWORD | Auto-generated | PostgreSQL password |
trustgate-secrets | DATABASE_NAME | External DB | PostgreSQL database |
trustgate-secrets | DATABASE_URL | External DB | Connection URL |
trustgate-secrets | NEURAL_TRUST_FIREWALL_URL | Firewall integration | Firewall base URL |
trustgate-secrets | NEURAL_TRUST_FIREWALL_SECRET_KEY | Firewall integration | Firewall API key |
Firewall
Created when neuraltrust-firewall.firewall.enabled: true:
| Kubernetes Secret | Key | Required | Description |
|---|
firewall-secrets | JWT_SECRET | Yes | Shared with services calling the firewall |
firewall-secrets | HUGGINGFACE_TOKEN | No | HF token for model weights |
Align controlPlane.secrets.firewallJwtSecret (FIREWALL_JWT_SECRET) with firewall-secrets.JWT_SECRET so the Control Plane validates firewall tokens correctly.
Docker registry
| Kubernetes Secret | Type | Required | Description |
|---|
gcr-secret | docker-registry | Yes | Credentials for NeuralTrust container images |
Secret references in values
You can embed a secret value directly in values (less secure) or reference an existing secret (recommended).
neuraltrust-data-plane:
dataPlane:
secrets:
dataPlaneJWTSecret: "your-secret-value"
neuraltrust-data-plane:
dataPlane:
secrets:
dataPlaneJWTSecret:
secretName: "data-plane-jwt-secret"
secretKey: "DATA_PLANE_JWT_SECRET"
TrustGate ↔ Firewall integration
TrustGate calls the NeuralTrust Firewall using two keys stored in trustgate-secrets:
trustgate:
global:
env:
NEURAL_TRUST_FIREWALL_URL: "http://firewall-gateway:8000"
NEURAL_TRUST_FIREWALL_SECRET_KEY: "your-secret-key"
When global.autoGenerateSecrets: true, both keys are auto-populated. With preserveExistingSecrets: true, you must add them to a pre-created trustgate-secrets.
After changing firewall secrets, restart TrustGate so pods pick up the new values:
kubectl rollout restart deployment/trustgate-control-plane -n neuraltrust
kubectl rollout restart deployment/trustgate-data-plane -n neuraltrust
kubectl rollout restart deployment/trustgate-actions -n neuraltrust
External Secrets Operator
Example syncing the Data Plane JWT from Vault:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: data-plane-jwt-secret
namespace: neuraltrust
spec:
secretStoreRef:
name: vault-backend
kind: SecretStore
target:
name: data-plane-jwt-secret
data:
- secretKey: DATA_PLANE_JWT_SECRET
remoteRef:
key: neuraltrust/data-plane/jwt-secret
Apply equivalent ExternalSecret resources for every entry in the reference table you intend to manage externally, then deploy the chart with global.preserveExistingSecrets: true.
Pre-generation script
For environments that require all secrets created before deployment, the chart repo ships a helper script:
git clone --branch v<VERSION> --depth 1 https://github.com/NeuralTrust/neuraltrust-platform.git
cd neuraltrust-platform
# Interactive prompts
./create-secrets.sh --namespace neuraltrust
# Non-interactive via environment variables
export DATA_PLANE_JWT_SECRET="..."
export CONTROL_PLANE_JWT_SECRET="..."
export TRUSTGATE_JWT_SECRET="..."
export SERVER_SECRET_KEY="$TRUSTGATE_JWT_SECRET" # must match
export POSTGRES_PASSWORD="..."
./create-secrets.sh --namespace neuraltrust
# Replace any existing secrets without prompting
./create-secrets.sh --namespace neuraltrust --replace-existing
After the script completes, deploy with preserveExistingSecrets: true.
Security best practices
- Default to auto-generation. It’s the safest starting point and the chart preserves existing secrets on every upgrade.
- Use external secret management for production. Vault, Sealed Secrets, or External Secrets Operator give you rotation, audit, and centralized control.
- Never commit secret values to git. Don’t store real values in version-controlled values files; use
--set or pre-created secret references.
- Rotate secrets regularly — especially JWT secrets. Restart the affected workloads after each rotation.
- Restrict access with RBAC. Limit who can
get/list/watch Kubernetes Secrets in the neuraltrust namespace.
Troubleshooting
Secret not found
kubectl get secret <secret-name> -n neuraltrust
kubectl get secrets -n neuraltrust
kubectl get secret <secret-name> -n neuraltrust -o yaml
TrustGate firewall env vars not updating
TrustGate reads NEURAL_TRUST_FIREWALL_URL and NEURAL_TRUST_FIREWALL_SECRET_KEY from trustgate-secrets at pod startup. After helm upgrade, restart TrustGate deployments to reload them (see TrustGate ↔ Firewall integration above).
Wrong secret value
kubectl patch secret <secret-name> -n neuraltrust \
--type='json' \
-p='[{"op": "replace", "path": "/data/<key>", "value": "<base64-encoded-value>"}]'
When creating secrets manually:
- URL-encode passwords inside
DATABASE_URL.
- Base64-encode all values stored in Kubernetes Secrets.
- Avoid trailing newlines or whitespace — they break authentication silently.