# Helm values reference

All configurable values for the `crypto-vault` Helm chart, grouped by concern. Values marked **required** have no safe default and the chart will not produce a working installation without them.

:::note
The chart pins a tested image set. Keep those defaults for normal released deployments. Per-service
overrides are available for image sets explicitly qualified with Sodot; if you use them, keep all
affected service tags on the qualified compatible set.
:::

:::note\[Using the example Terraform?]
`terraform apply` writes a complete values file per cluster to `./out/cluster-{0,1,2}.values.yaml`, with everything below already filled in (database, KMS, workload identity, shard identities, cross-cluster addresses). This page is for understanding those values, customizing them, or authoring your own when you [bring your own infrastructure](/deploy/provision-infra).
:::

## Service toggles

The chart enables all six components by default. The two-stage install controls them per cluster: the example Terraform's spoke values keep only Key Store + Policy Server (the four hub-only services are set to `false` there), and the hub comes up in two stages - Stage 1 installs it with the hub-only services turned off (`--set ...enabled=false`), then Stage 2 leaves them at their default. See [Install with Helm](/deploy/helm/install).

| Value | Type | Default | Description |
|---|---|---|---|
| `cloudProvider` | string | `aws` | Key Store runtime and KMS provider. Set `aws` for Nitro Enclaves with AWS KMS or `gcp` for GKE Confidential Nodes with Cloud KMS. |
| `gcp` | object | provider defaults | GCP-specific Key Store scheduling, resources, and environment settings. Used only when `cloudProvider=gcp`; see [GCP Confidential Node configuration](#gcp-confidential-node-configuration). |
| `services.keyStore.enabled` | bool | `true` | Enable the Key Store on this cluster. |
| `services.policyServer.enabled` | bool | `true` | Enable the Policy Server on this cluster. |
| `services.orchestrator.enabled` | bool | `true` | Enable the Orchestrator. Hub only - set `false` on spokes and in Stage 1. |
| `services.relay.enabled` | bool | `true` | Enable the relay. Hub only - set `false` on spokes and in Stage 1. |
| `services.nats.enabled` | bool | `true` | Enable NATS. Hub only - set `false` on spokes and in Stage 1. |
| `services.vaultUi.enabled` | bool | `true` | Enable the web UI. Hub only - set `false` on spokes and in Stage 1. |

## MPC shard identity (required, per shard, sensitive)

Each cluster's shard is configured here, from its database connection to its key-handling material. The sensitive values (such as the imported-key encryptor private key) must never be committed to version control; supply them from a secrets manager or encrypted values file. The shard's own identity keypair is generated on first boot, not supplied here.

| Value | Description |
|---|---|
| `mpcKeyStores[].mpcShardIndex` | **Required, unique per cluster.** 0-based shard index (`0`/`1`/`2`). Sets the Key Store's self-name `mpc-ks-<index>`, which the Orchestrator matches to build its cluster-identity set. If omitted it falls back to the entry's position in the list - so with one shard per cluster every cluster collapses to `mpc-ks-0`, the shards collide, and the mesh never forms. |
| `mpcKeyStores[].name` | Kubernetes resource/label name only, e.g. `mpc-key-store-0`. Does **not** set the shard's MPC identity - use `mpcShardIndex` for that. |
| `mpcKeyStores[].backupEncryptorPublicKey` | X25519 HPKE public key (raw 32-byte, lowercase-hex, 64 chars) wired into the shard's backup encryptor. Key creation and import are blocked until this is set. See [generating the backup key pair](/deploy/operations/backup-dr#generating-the-backup-key-pair). |
| `mpcKeyStores[].importedKeyEncryptorPem` | RSA private key for unwrapping imported keys. |
| `mpcKeyStores[].importedKeyEncryptorPemSecret.existingSecret` / `.key` | Optional GitOps alternative to the inline `importedKeyEncryptorPem`: name of an existing Secret holding the RSA key (data key defaults to `imported-key-pem`). Substituted into the Key Store config at pod startup so the PEM never lands in Helm values. |
| `mpcKeyStores[].dbHost` / `.dbPort` / `.dbName` | Connection details for this shard's Key Store database. |
| `remoteMpcKeyStores[].url` | Hub only. Private endpoint URL of a remote shard's Key Store (no Deployment rendered; the Orchestrator dials this URL). |
| `remoteMpcKeyStores[].mpcShardIndex` | Hub only. The remote shard's own 0-based index. The Orchestrator names its gateway entry `mpc-ks-<index>` to match that shard's Key Store self-name. Set it whenever a remote shard's index differs from its position in this list (e.g. the default hub=shard 1 topology, whose remotes are shards 0 and 2); if omitted it falls back to list position and mis-names the higher shard. |

## Policy Server identity (required, per shard)

| Value | Description |
|---|---|
| `policyServers[].name` | **Required, unique per cluster** on a multi-cluster deployment. The shard's MPC identity - the reader's `cluster_identity_name` and the Orchestrator's `policy_gw` reader entry (approvals are verified by this name). Keep the literal `reader` substring, e.g. `policy-server-reader-0`. Distinct from `writerName`/`readerName`; if omitted, every single-shard cluster falls back to `policy-server-reader-0` and the cross-cluster approval mesh breaks. |
| `policyServers[].writerName` / `.readerName` | Kubernetes resource names for the writer and reader halves. |
| `policyServers[].db.host` / `.port` / `.name` | Connection details for this shard's Policy Server database. |
| `remotePolicyServers[]` | Hub only. Reader/writer URL entries for remote shard Policy Servers, reachable through your cloud's private service endpoints. |
| `policyServer.approvalThreshold` | The number of human Setup Admins who must approve a change to the initial Administrative Policy (the admin enrollment quorum). This is a human-approval count, not the Policy Server (MPC) signature threshold. Only a value of `1` is currently supported. |
| `policyServers[].externalRuleServerSigningKey` | **Required at install** - inline here, or via `externalRuleServerSigningKeySecret.existingSecret` below. The render fails when both are empty and the Policy Server refuses to boot without it, **even when no ERS is configured**. The private key the Policy Server uses to sign its requests to an [External Rule Server](/guide/signing-policy/external-rule-server); the matching public key is published at `GET /system-info`. Nothing is auto-generated. |
| `policyServers[].externalRuleServerSigningKeySecret.existingSecret` / `.key` | Source the ERS signing key from an existing Secret (PKCS#8 EC PEM) instead of inlining it above - the GitOps path. Data key defaults to `signing-key`; substituted into the config at pod startup so the key never lands in Helm values. |

## Bootstrap and Setup Admins

| Value | Type | Default | Description |
|---|---|---|---|
| `googleAuth.adminEmails` | list | (required) | Email addresses of the Google Setup Admins. **Set on every cluster running a Policy Server** (all three), not just the hub - each Policy Server seeds them into its own admin allowlist. These accounts are prompted to enroll a passkey on first sign-in. |
| `googleAuth.clientId` | string | (required) | Google OAuth client ID. **Set on every cluster running a Policy Server** (all three), not just the hub. Each Policy Server reader verifies admin-login JWTs against it; a spoke left without it rejects the token (`Trying to verify token for disabled Google issuer`), so the cross-shard login-approval quorum can never be met and first login fails. Only the client **secret** below (`ui.clientSecretRef`) is hub-only. |
| `ui.clientSecretRef` | string | `""` | Name of an existing Secret (release namespace) holding the UI's Google OAuth **client secret** under key `CLIENT_SECRET`, mounted into the UI pod - the secret half of the pair whose public half is `googleAuth.clientId`. Required for Google login when bringing your own infrastructure; the example Terraform wires it automatically. |
| `policyServer.initialAdmins` | list | (required) | Non-Google **human** Setup Admins seeded at bootstrap (Google admins are derived from `googleAuth.adminEmails` and appended automatically). Only `type: human` is accepted - the render fails on any other type. Machine users and agents are **not** seeded here; they are created at runtime via the API. Each entry requires `cloudProvider`, `id` (UUID), `name`, `publicKey`, `email` (or `emailEnv`), and `type: human`. |

## Database

| Value | Description |
|---|---|
| `postgresql.credentials.username` | Application PostgreSQL role name. |
| `postgresql.credentials.password` | Application role password. Supply from a secrets manager. |
| `postgresql.credentials.existingSecret` / `.usernameKey` / `.passwordKey` | GitOps / External Secrets Operator alternative: name of an existing Secret (release namespace) holding the DB username and password. When set, the inline `password` is ignored and never embedded in rendered Secrets; credentials are read via `secretKeyRef` at pod startup. `usernameKey` / `passwordKey` name the keys within that Secret (default `username` / `password`). |
| `postgresql.sslMode` | Set to `require` when RDS enforces TLS; leave empty otherwise. |
| `orchestrator.db.host` / `.port` / `.name` | Orchestrator database connection. Hub cluster only. |
| `keyStore.dbCredentials` | Optional Key Store-specific credentials. Empty inherits `postgresql.credentials`. GCP Terraform supplies the Key Store's Workload Identity IAM database username and an empty password. |
| `orchestrator.db.credentials` | Optional Orchestrator-specific credentials. Empty inherits `postgresql.credentials`. GCP Terraform supplies the Orchestrator's Workload Identity IAM database username and an empty password. |
| `policyServer.db.readerCredentials` / `.writerCredentials` | Optional, distinct credentials for the Policy Server reader and writer. Empty inherits `postgresql.credentials`. GCP Terraform supplies a separate Workload Identity IAM database username for each role and an empty password. |

## Storage encryptor (required for production)

| Value | Default | Description |
|---|---|---|
| `keyStore.storageEncryptor.type` | `""` | Optional compatibility assertion for older values files. When set, it must match the authoritative `cloudProvider` (`aws_kms` or `gcp_kms`). |
| `keyStore.storageEncryptor.awsKms.keyId` | (required when `cloudProvider=aws`) | KMS key ARN. |
| `keyStore.storageEncryptor.awsKms.region` | (required when `cloudProvider=aws`) | AWS region of the KMS key. |
| `gcp.kmsKey` | (required when `cloudProvider=gcp`) | Full Cloud KMS CryptoKey resource name: `projects/PROJECT/locations/LOCATION/keyRings/RING/cryptoKeys/KEY`. The chart renders this canonical value as `[storage_encryptor.gcp_kms].kms_key`. |

## AWS Nitro Enclave configuration

| Value | Default | Description |
|---|---|---|
| `aws.region` | (required) | AWS region for the credential-fetcher. |
| `aws.nitroEnclave.enclave.cpuCount` | `"2"` | vCPUs allocated to the enclave. |
| `aws.nitroEnclave.enclave.memoryMiB` | `"2048"` | Memory allocated to the enclave in MiB (must be a multiple of 1024). |
| `aws.nitroEnclave.enclave.debug` | `"false"` | Must remain `"false"` in production. Debug mode zeroes PCR attestation values. |
| `aws.nitroEnclave.hugepages.size` | `"1Gi"` | Must match the Nitro node allocator page size. |
| `aws.nitroEnclave.nodeSelector` | `aws-nitro-enclaves-k8s-dp: enabled` | Selects Nitro-capable nodes for Key Store workloads. |
| `mpcKeyStoreServiceAccount.aws.roleArn` | (required) | IRSA role ARN granting `kms:Decrypt` and `kms:GenerateDataKey`. |
| `aws.nitroEnclave.registrySecret.name` / `.userKey` / `.passwordKey` | `artifactory-credentials` / `username` / `password` | Secret with **plaintext** username/password keys that the Key Store runner and the fetch-EIF init container use to `oras login` and pull the enclave image / measured EIF. This is **distinct** from the `artifactory-dockerconfig` dockerconfigjson imagePullSecret the kubelet uses for the sidecar images - provision both. |

## GCP Confidential Node configuration

| Value | Default | Description |
|---|---|---|
| `mpcKeyStoreImage.nonAws` / `mpcKeyStoreImage.nonAws.repository` / `mpcKeyStoreImage.nonAws.tag` | chart-pinned | Image settings, repository, and immutable release tag for the non-AWS MPC Key Store used on GCP Confidential Nodes. |
| `gcp.confidentialNode` | scheduling defaults | Scheduling controls for the GCP Key Store's Confidential node pool. |
| `gcp.confidentialNode.nodeSelector` | `{}` | Additional labels for Key Store placement. The chart always injects the reserved `cloud.google.com/gke-confidential-nodes-instance-type=SEV_SNP` selector and rejects attempts to override it. |
| `gcp.confidentialNode.tolerations` | `[]` | Tolerations for the Confidential node pool's taints. |
| `gcp.cloudSqlProxy` | chart defaults | Per-pod Cloud SQL Auth Proxy sidecar configuration for every database workload. GCP Terraform supplies the instance connection name, private-IP mode, image, and resources. |
| `gcp.cloudSqlProxy.instanceConnectionName` | `""` | Cloud SQL instance connection name in `project:region:instance` form. |
| `gcp.cloudSqlProxy.privateIp` | `""` | Private IP of the Cloud SQL instance. Set by the GCP Terraform module. |
| `gcp.cloudSqlProxy.autoIamAuthn` | `true` | Use automatic IAM database authentication. Disable only with the supported generated-password fallback. |
| `gcp.cloudSqlProxy.image` / `gcp.cloudSqlProxy.resources` | chart defaults | Proxy image and resource configuration objects. |
| `gcp.cloudSqlProxy.image.repository` / `gcp.cloudSqlProxy.image.tag` / `gcp.cloudSqlProxy.image.digest` | chart-pinned | Proxy image coordinates. A non-empty digest takes precedence over the immutable tag. |
| `gcp.cloudSqlProxy.resources.requests` / `gcp.cloudSqlProxy.resources.limits` | chart defaults | Proxy sidecar CPU and memory request and limit objects. |
| `gcp.cloudSqlProxy.resources.requests.cpu` / `gcp.cloudSqlProxy.resources.requests.memory` | `50m` / `64Mi` | Proxy sidecar resource requests. |
| `gcp.cloudSqlProxy.resources.limits.cpu` / `gcp.cloudSqlProxy.resources.limits.memory` | `500m` / `256Mi` | Proxy sidecar resource limits. |
| `gcp.keyStore` | chart defaults | Container configuration for the GCP Key Store. |
| `gcp.keyStore.configPath` | `/config/mpc-ks-config.toml` | Mounted Key Store configuration path. |
| `gcp.keyStore.resources` | chart defaults | CPU and memory requests and limits for the GCP Key Store container. |
| `gcp.keyStore.resources.requests` / `gcp.keyStore.resources.limits` | chart defaults | Resource request and limit maps for the GCP Key Store container. |
| `gcp.keyStore.resources.requests.cpu` / `gcp.keyStore.resources.requests.memory` | chart defaults | CPU and memory requests for the GCP Key Store container. |
| `gcp.keyStore.resources.limits.cpu` / `gcp.keyStore.resources.limits.memory` | chart defaults | CPU and memory limits for the GCP Key Store container. |
| `gcp.keyStore.extraEnv` | `[]` | Additional Kubernetes environment variables for the GCP Key Store container. |
| `mpcKeyStoreServiceAccount.gcp.serviceAccountEmail` | (required) | GCP service account email used by the Key Store and its Cloud SQL Auth Proxy through Workload Identity, including Cloud KMS access. |
| `orchestrator.serviceAccount` | chart defaults | Orchestrator Kubernetes ServiceAccount configuration. |
| `orchestrator.serviceAccount.gcp` | `{}` | Orchestrator GCP Workload Identity configuration. |
| `orchestrator.serviceAccount.automountServiceAccountToken` / `orchestrator.serviceAccount.annotations` / `orchestrator.serviceAccount.gcp.serviceAccountEmail` | `false` / `{}` / `""` | Orchestrator Kubernetes ServiceAccount settings. On GCP, set the workload-specific GSA email used by the pod and its Cloud SQL Auth Proxy sidecar. |
| `policyServer.readerServiceAccount` | chart defaults | Policy Server reader Kubernetes ServiceAccount configuration. |
| `policyServer.readerServiceAccount.gcp` | `{}` | Policy Server reader GCP Workload Identity configuration. |
| `policyServer.readerServiceAccount.automountServiceAccountToken` / `policyServer.readerServiceAccount.annotations` / `policyServer.readerServiceAccount.gcp.serviceAccountEmail` | `false` / `{}` / `""` | Policy Server reader Kubernetes ServiceAccount settings and workload-specific GSA email. |
| `policyServer.writerServiceAccount` | chart defaults | Policy Server writer Kubernetes ServiceAccount configuration. |
| `policyServer.writerServiceAccount.gcp` | `{}` | Policy Server writer GCP Workload Identity configuration. |
| `policyServer.writerServiceAccount.automountServiceAccountToken` / `policyServer.writerServiceAccount.annotations` / `policyServer.writerServiceAccount.gcp.serviceAccountEmail` | `false` / `{}` / `""` | Policy Server writer Kubernetes ServiceAccount settings and workload-specific GSA email. |

## Cross-cluster connectivity

| Value | Description |
|---|---|
| `nats.hub.url` | Spoke clusters: NATS endpoint URL on the hub, e.g. `nats://nats.hub.<private-hostname>:4222`. |
| `nats.internalLoadBalancer.enabled` | Hub: expose NATS through an internal load balancer for AWS PrivateLink or GCP Private Service Connect. |
| `nats.internalLoadBalancer.annotations` | Cloud-specific Service annotations that make the NATS load balancer internal-only. **Required when `enabled: true`**. Without the correct provider annotations, the service may become public. |
| `nats.streamReplication.credentialsSecret` | Name of a Secret holding a NATS `.creds` file for a JetStream-admin account (mounted at `/creds`; data key from `nats.streamReplication.credentialsKey`, default `nats.creds`). Set this when NATS operator-JWT auth is enabled so the post-install stream-replication Job can authenticate; empty uses the default unauthenticated connection. |
| `nats.config.merge` | Strategic-merge overlay onto the NATS subchart config. Selects alternate cross-cluster/security topologies: leaf-node (`leafnodes.remotes`) and operator-JWT auth + TLS (`operator`/`resolver`). See `bootstrap/nats-leaf-topology.example.yaml` and `nats-jwt-auth.example.yaml`. Not shown in the chart's generated values (it's a subchart passthrough). |
| `relay.address` | Client-side relay endpoint. Hub: `http://relay-router:80`. Spokes: private endpoint URL from AWS PrivateLink or GCP Private Service Connect. |
| `relay.apiKey` | Relay API key (client side). |
| `relay.relay.apiKey` | Server-side relay key on the hub, passed through to the relay subchart's `RELAY_API_KEYS`. Must be the bare `relay.apiKey` client key plus the `,AdminKey` role suffix, e.g. `<key>,AdminKey`. **Required** when the hub deploys the relay subchart (Stage 2) - the render fails without it. |
| `relay.cloudProvider` | Optional compatibility override for relay-subchart provider-specific features. Leave empty to inherit the top-level `cloudProvider`; if set, it must match the parent value. |
| `relay.apiKeySecret.existingSecret` / `.key` | Source the client relay API key from an existing Secret instead of inline `relay.apiKey` (GitOps). Data key defaults to `relay-api-key`; substituted at pod startup so nothing secret lands in the rendered config. |
| `orchestrator.internalLoadBalancer.enabled` / `.annotations` | Hub. Expose the Orchestrator API on a cloud-agnostic internal load-balancer Service instead of, or alongside, ingress - the chart-native "expose the Services yourself" path when the Terraform front-door NLB is disabled. `.annotations` carry the cloud-specific internal-LB knobs (as for `nats.internalLoadBalancer.annotations`). On GCP, its source ranges come from `networkPolicies.extraIngressCidrs`. Off by default. |
| `policyServers[].internalLoadBalancer.enabled` / `.annotations` / `.reader.annotations` / `.writer.annotations` | Spoke. Expose this shard's Policy Server reader and writer through internal load balancers for the hub Orchestrator to reach over AWS PrivateLink or GCP Private Service Connect. Per-role annotations merge over the shared base. On GCP, enabling either Service without its matching non-empty PSC ingress CIDRs fails the chart render. |
| `networkPolicies.enabled` | Render Kubernetes NetworkPolicies (deny-all baseline plus Pod-selector and `extraIngressCidrs` ingress). Default `true`; set `false` only for single-cluster Pod-only topologies or a CNI that does not enforce NetworkPolicies. |
| `networkPolicies.extraIngressCidrs` | Shared ingress CIDRs admitted by NetworkPolicies. AWS and direct-routing topologies may need their VPC or load-balancer source CIDRs here. On GCP PSC, keep service-specific PSC NAT subnets out of this broad list and use `gcpPscIngressCidrs` instead. |
| `networkPolicies.extraNatsEgressCidrs` | Additional CIDRs that Policy Server writers and other NATS clients may reach on TCP/4222. Add the narrow endpoint subnet when NATS is reached through AWS PrivateLink or GCP Private Service Connect. |
| `networkPolicies.gcpPscIngressCidrs` | Role-specific GCP PSC producer ingress CIDRs. |
| `networkPolicies.gcpPscIngressCidrs.keyStore` / `networkPolicies.gcpPscIngressCidrs.policyReader` / `networkPolicies.gcpPscIngressCidrs.policyWriter` / `networkPolicies.gcpPscIngressCidrs.nats` | GCP only. Role-specific ingress CIDRs for the matching workload NetworkPolicies. If you enable a chart-owned internal LoadBalancer, the same list populates its `loadBalancerSourceRanges` and the Service preserves source IPs with `externalTrafficPolicy: Local`. The GCP example instead pre-creates the matching Terraform-owned Services with the same source ranges, and leaves the chart's internal-load-balancer switches off. Keep each list limited to that service attachment's PSC NAT subnet and the Google load-balancer health-check ranges. |
| `networkPolicies.gcpPscIngressCidrs.relay` | GCP only. Relay producer ingress for the NetworkPolicy. The GCP example applies the same CIDRs to its Terraform-owned relay LoadBalancer Service. If you bring your own infrastructure, enforce those source ranges on the relay Service yourself. |
| `networkPolicies.gcpRelayEgressCidrs` | GCP spokes only. Stable relay PSC endpoint CIDRs, normally `/32`, allowed when the local relay Service is disabled. |
| `networkPolicies.gcpRemoteKeyStoreEgressCidrs` | GCP hub only. Remote spoke Key Store PSC endpoint CIDRs allowed for Orchestrator egress. |
| `networkPolicies.gcpRemotePolicyEgressCidrs` | GCP hub only. Remote spoke Policy Server PSC endpoint CIDRs allowed for Orchestrator egress. |
| `networkPolicies.publicHttpsEgress` | Public HTTPS egress settings for workloads that call external identity and pricing services. |
| `networkPolicies.publicHttpsEgress.enabled` | Allow the UI and Policy Server readers to reach required public HTTPS services such as Google OAuth and JWKS. The GCP Terraform example enables this. Leave disabled when an egress proxy or gateway enforces a narrower hostname allowlist. |

## Ingress and TLS (hub cluster only)

| Value | Description |
|---|---|
| `orchestrator.ingress.enabled` / `.host` / `.className` | Orchestrator Ingress resource. Use your installed controller, including `gce` or `gce-internal` on GCP. |
| `orchestrator.ingress.tls` | TLS configuration for the Orchestrator Ingress. |
| `orchestrator.ingress.annotations` | Annotations for the Orchestrator Ingress (e.g. `cert-manager.io/cluster-issuer`, NGINX/ALB controller config). Controller/cert annotations are effectively required on the `alb`/`nginx` path. |
| `orchestrator.ingress.gcpManagedCertificate` | GCP-managed certificate settings for the Orchestrator Ingress. |
| `orchestrator.ingress.gcpManagedCertificate.enabled` | GCP only. Create and attach a Google-managed certificate for the Ingress host. Do not enable the Kubernetes TLS secret path at the same time. |
| `ui.ingress.enabled` / `.host` / `.className` | UI Ingress resource. |
| `ui.ingress.tls` | TLS configuration for the UI Ingress. |
| `ui.ingress.annotations` | Annotations for the UI Ingress (e.g. `cert-manager.io/cluster-issuer`, controller config). |
| `ui.ingress.gcpManagedCertificate` | GCP-managed certificate settings for the UI Ingress. |
| `ui.ingress.gcpManagedCertificate.enabled` | GCP only. Create and attach a Google-managed certificate for the UI host. The hostname must resolve to the GKE Ingress IP before the certificate becomes active. |
| `ui.loadBalancer.enabled` / `.host` / `.certificateArn` / `.scheme` | AWS only. Alternative to `ui.ingress`: expose the UI through a chart-managed NLB serving HTTPS:443 with an ACM certificate. The GCP path uses `ui.ingress`. |

## Replicas and high availability

| Value | Default | Description |
|---|---|---|
| `keyStore.replicas` | `2` | Active Key Store replicas per shard. On AWS, each replica requires a Nitro-capable node. On GCP, each replica schedules onto the selected Confidential node pool. |
| `orchestrator.replicas` | `2` | Active-active Orchestrator replicas. |
| `policyServer.replicas` | `2` | Policy Server reader and writer replicas. |
| `ui.replicas` | `2` | UI replicas. |
| `autoscaling.enabled` | `false` | Enable HPA for stateless services (Orchestrator, Policy Server, UI). Requires the metrics-server addon. |
| `autoscaling.minReplicas` / `.maxReplicas` | `2` / `6` | HPA replica bounds. |
| `podDisruptionBudgets.enabled` | `true` | PDB with `maxUnavailable: 1` for all roles. |
| `orchestrator.maxConnections` | `500` | Orchestrator request-concurrency cap - bounds concurrent in-flight signing ceremonies. Above it, requests get 503s / throughput plateaus. A compute/throughput lever alongside replicas and HPA (it does not size the Postgres pool). |
| `nats.streamReplication.enabled` | `true` | Post-install/upgrade Job on the hub that raises the JetStream stream replica factor to R3 for HA (the app creates streams at R1). No-op on spokes and single-node dev. |

## Node placement and scheduling

By default the stateless services carry a soft anti-affinity that spreads their replicas across availability zones and nodes, with no node-group constraint. To steer a service onto a dedicated node group (for example, nodes labeled `purpose=cv-general`), set any of the values below. `nodeAffinity` expresses a preferred or required node match and merges with the built-in anti-affinity. `nodeSelector` is a hard label match. `tolerations` let the pods schedule onto tainted nodes. All three default to no constraint, so existing installs are unaffected.

The Key Store is not listed here. AWS pins it with `aws.nitroEnclave.nodeSelector`; GCP pins it with
`gcp.confidentialNode.nodeSelector`. The relay service exposes its own `nodeAffinity` and `tolerations`
values, and NATS is scheduled through `nats.podTemplate`.

| Value | Default | Description |
|---|---|---|
| `orchestrator.nodeAffinity` | `{}` | Node affinity for the Orchestrator pods, merged with the built-in anti-affinity. |
| `orchestrator.nodeSelector` | `{}` | Node label selector (hard constraint) for the Orchestrator pods. |
| `orchestrator.tolerations` | `[]` | Tolerations for the Orchestrator pods. |
| `policyServer.nodeAffinity` | `{}` | Node affinity for the Policy Server pods. Applies to both the reader and writer, on every shard. |
| `policyServer.nodeSelector` | `{}` | Node label selector (hard constraint) for the Policy Server pods. |
| `policyServer.tolerations` | `[]` | Tolerations for the Policy Server pods. |
| `ui.nodeAffinity` | `{}` | Node affinity for the UI pods. |
| `ui.nodeSelector` | `{}` | Node label selector (hard constraint) for the UI pods. |
| `ui.tolerations` | `[]` | Tolerations for the UI pods. |

## Web UI feature flags

The UI ships with every area enabled. Set a flag to `"false"` to hide that area from the web UI (rendered as `NEXT_PUBLIC_FEATURE_*` env in the UI ConfigMap). Values are strings.

| Value | Default | Description |
|---|---|---|
| `ui.nextPublicFeatureAdminPolicy` / `RiskPolicy` / `AddressBook` / `UserManagement` / `Keys` / `Settings` / `AuditLogs` / `Approval` / `Vaults` | `"true"` | Per-area UI feature toggles. Set `"false"` to hide the corresponding area. |
| `ui.nextPublicIsDemo` | `"false"` | Enables the UI's demo mode. Keep `"false"` for production. |

## Observability

OpenTelemetry (OTLP) export for the Orchestrator and Policy Server reader/writer. Export is **off until
`observability.otlp.endpoint` is set**. On AWS, Key Store telemetry uses the separate
`aws.nitroEnclave.otlpExporter` path. On GCP, configure the Key Store container independently through
its supported environment settings.

| Value | Default | Description |
|---|---|---|
| `observability.otlp.endpoint` | `""` | OTLP collector endpoint (must start with `http://` or `https://`). Setting it enables export. With `protocol: grpc` the URL is used verbatim (e.g. `http://collector:4317`); with `http/protobuf` the SDK appends `/v1/logs`. An `https://` scheme negotiates TLS. |
| `observability.otlp.protocol` | `grpc` | Transport: `grpc` or `http/protobuf`. |
| `observability.otlp.headersSecret.name` / `.key` | `""` | Existing Secret supplying collector auth headers (e.g. a bearer token), taken verbatim as a comma-separated `key=value` list. Requires an `https://` endpoint - the render fails otherwise. |
| `observability.otlp.logFilter` | `""` | Verbosity of the logs exported over OTLP (tracing `EnvFilter` syntax, e.g. `warn,policy_server=debug`), independent of `RUST_LOG` (which controls only stdout). Empty uses the default. |
| `observability.otlp.console` | `true` | Keep logging to stdout in addition to OTLP. Set `false` for OTLP-only. |
| `observability.otlp.resourceAttributes` | `""` | Extra OTel resource attributes, comma-separated `key=value` (e.g. `deployment.environment=prod`). |
| `observability.otlp.timeout` | `""` | Per-export timeout in milliseconds (quote it). Empty uses the SDK default. |
| `orchestrator.rustLog` / `keyStore.rustLog` / `policyServer.readerRustLog` / `policyServer.writerRustLog` | `"info"` | Console (stdout) log filter (`RUST_LOG`) per service, in tracing `EnvFilter` syntax. Independent of the OTLP sink filter (`observability.otlp.logFilter`); set `debug`/`trace` to troubleshoot. |
