{
  "schema_version": 2,
  "id": "operate/iris/context-gateway/self-managed/configuration",
  "title": "Configure self-managed Context Gateway",
  "url": "https://redis.io/docs/latest/operate/iris/context-gateway/self-managed/configuration/",
  "summary": "Prepare Helm values, file references, credential storage, the database registry, Context Surface databases, and optional features for Context Gateway.",
  "tags": [
    "docs",
    "operate",
    "iris"
  ],
  "last_updated": "2026-11-04T01:09:56+02:00",
  "page_type": "content",
  "content_hash": "88108fc3c932951b80913662b7c5258edb198b8d54c63c4c82b4ad7bf07cd8d5",
  "sections": [
    {
      "id": "overview",
      "title": "Overview",
      "role": "overview",
      "text": "The chart reads non-secret settings from Helm values and every credential from a mounted file. Prepare `gateway-values.yaml` and the Secrets it references before you [deploy](https://redis.io/docs/latest/operate/iris/context-gateway/self-managed/deploy)."
    },
    {
      "id": "helm-values",
      "title": "Helm values",
      "role": "content",
      "text": "| Value | Purpose |\n| --- | --- |\n| `tenantId` | The deployment's tenant. Must equal the Identity Service's single tenant ID. |\n| `publicBaseURL` | The Data Plane's public origin, used in every Gateway's `mcpEndpoint`. |\n| `dataplane.ingress` | An optional Ingress for the Data Plane, off by default. When on, `hosts` must include the `publicBaseURL` host and route `/` with `pathType: Prefix`, `annotations` must not rewrite paths, and for an `https://` origin a `tls` entry with a `secretName` must cover that host. Otherwise rendering fails. |\n| `identityService.baseURL` | The Identity Service runtime API that checks agent keys. |\n| `identityService.allowInsecureTransport` | `true` for an in-cluster `http://` Identity Service URL. Default `false`. |\n| `profile` | `prod` (default) or `dev` logging. |\n| `security.profile` | `standard` (default) or `fips`. See [Configure security and tracing](#configure-security-and-tracing). |\n| `imagePullSecrets` | Credentials for a private registry. |\n| `controlplane.config`, `dataplane.config` | Application settings for each plane. They default to `{}`, so the image's defaults apply. |\n\nStart with the complete [deployment values example](https://redis.io/docs/latest/operate/iris/context-gateway/self-managed/deploy#3-create-gateway-valuesyaml), then apply the settings you need from this page."
    },
    {
      "id": "file-references",
      "title": "File references",
      "role": "content",
      "text": "Each credential has its own reference. Point it at a Kubernetes Secret key with `existingSecret` and `secretKey`, or at a Secrets Store CSI (Container Storage Interface) Driver file with `secretProviderClass` and `fileName`. Change a reference's optional `checksum` to roll out both planes after the file changes.\n\n| Helm reference | File contents | Used by |\n| --- | --- | --- |\n| `metadataDB` | YAML with `metadata_db.urls`. | Both planes |\n| `credentialStorage.redis.connection` | YAML with `credential_storage.redis.urls`. | Both planes |\n| `credentialStorage.redis.keyring` | Directory of base64-encoded 32-byte Advanced Encryption Standard (AES) keys. | Both planes |\n| `credentialStorage.vault.credentials.controlplane` | YAML with `credential_storage.vault.token`, with write and delete access. | Control Plane |\n| `credentialStorage.vault.credentials.dataplane` | YAML with `credential_storage.vault.token`, with read access. | Data Plane |\n| `databaseConnections[]` | YAML with `databases.<id>.urls`. | Both planes |\n| `controlplane.adminToken` | The admin token operators send to the Control Plane. | Control Plane |\n| `controlplane.internalToken` | The token Identity Service sends to the grant callback. | Control Plane |\n| `dataplane.runtimeToken` | The token the Data Plane sends to Identity Service. | Data Plane |\n| `license` | The license file as issued. | Both planes |\n| `tls.caBundle` | A PEM-encoded certificate authority (CA) bundle. | Both planes |\n\nThe admin, internal, and runtime tokens must be distinct. Keep the admin token, the internal token, and the Vault writer token out of every CSI class the Data Plane mounts."
    },
    {
      "id": "credential-storage",
      "title": "Credential storage",
      "role": "content",
      "text": "| `credentialStorage.provider` | Configuration |\n| --- | --- |\n| `redis` (default) | A credential Redis connection and a keyring shared by both planes. `credentialStorage.redis.keyFiles` maps each key ID to a file in the keyring, and `activeKeyId` names the key for new writes. |\n| `vault` | Separate writer and reader tokens for the Control Plane and Data Plane. |\n| `aws_secrets_manager` | Separate Identity and Access Management (IAM) roles for each plane through workload identity. |"
    },
    {
      "id": "database-registry",
      "title": "Database registry",
      "role": "content",
      "text": "Register every database a Gateway uses under the same ID on both planes: Context Surface databases and Gateway databases. Put display names in `shared.config.databases` and connection URLs in a `databaseConnections` file.\n\n[code example]\n\nBoth planes probe every entry at startup. An entry that does not answer does not stop a plane. The plane logs `Gateway database did not answer at startup` with the entry's `database_id` and starts."
    },
    {
      "id": "default-databases",
      "title": "Default databases",
      "role": "content",
      "text": "An on-prem deployment uses two databases from the registry, and each has its own job:\n\n| Database | Holds | Written by |\n| --- | --- | --- |\n| Gateway database | Cached results and compression handles. | Only the Gateway. |\n| Surface database | The customer data that Context Surfaces read. | Never the Gateway. |\n\nName a default for each so that administrators do not repeat the ID on every request. Both defaults are optional, and both planes read them:\n\n[code example]\n\nThe chart renders these as `default_databases.gateway` and `default_databases.surface` in the shared configuration. Each must name a `databases` entry, or both planes refuse to start.\n\n| Value | Used when | Without it |\n| --- | --- | --- |\n| `defaultDatabases.gatewayDb` | A Gateway create sends no `databaseId`. The response's `databaseBinding.databaseId` shows the resolved database, and `cache` and `compression` default to `ENABLED` as if you had sent it. An explicit `databaseId` always wins. | A Gateway created without `databaseId` has no database, so the cache and compression are off. |\n| `defaultDatabases.surfaceDb` | A Context Surface create sends no `contextSurface.databaseId`. An explicit ID always wins. | That create returns `400` on `contextSurface.databaseId`. |\n\nRedis Cloud has no defaults and always requires the database ID."
    },
    {
      "id": "gateway-database-requirements",
      "title": "Gateway database requirements",
      "role": "content",
      "text": "The Gateway database should use `maxmemory-policy noeviction`, and compression also needs the JSON module. The checks differ by where they run:\n\n- At startup, the Data Plane checks the default Gateway database. With `compression.enabled: true` it fails to start when the database does not use `noeviction` or has no JSON module. Otherwise it logs a warning. An unreachable default Gateway database only logs a warning at startup.\n- When you turn compression on through the API, the Control Plane checks the Gateway database. The request returns `400` on the `compression` field, with an `errors[]` entry whose `pointer` is `/compression` and whose `detail` explains the problem when the database cannot hold handles, and `503` when it cannot be reached. The same check runs when an update changes `databaseId` while compression stays `ENABLED`.\n- A Gateway create that omits `compression` never fails on this check. When the database cannot hold handles or cannot be reached, the Gateway is stored with `compression` set to `DISABLED`, and the Control Plane logs why.\n\nSize the Gateway database for the rate of stored results, their stored size, and their time to live (TTL). Compression ratios vary with the result, so measure stored bytes for your workload rather than assuming a fixed ratio. Account for cache entries and handles separately.\n\nThe cache applies its memory share and 90% guard when the eviction policy is `noeviction` or unreported. Handles require an explicitly reported `noeviction` policy before applying their share and headroom checks. An unreported policy disables handle creation.\n\nBy default, cache entries can use 40% of the Gateway database's `maxmemory` and handles can use 50%. `cache.max_memory_share` and `compression.max_memory_share` must each be positive and their sum must not exceed `0.95`, even when a feature is off. A separate database-wide guard stops new writes above 90% of `maxmemory`. These are admission limits, not Redis eviction policies. Use `maxmemory-policy noeviction` and set a finite, positive Redis `maxmemory`: when `maxmemory` is `0` (unlimited), neither the shares nor the 90% guard limits writes.\n\nWhen a limit is reached, the cache serves the Upstream result without storing a new entry. Automatic handle capture can return the result inline. An explicit `run_tool_as_handle` request that cannot store its result returns a tool error instead of the whole result. Release handles you no longer need, shorten TTLs, or increase database capacity. See [Tool call failures](https://redis.io/docs/latest/develop/ai/context-engine/context-gateway/limits#tool-call-failures).\n\nDo not point both defaults at the same Redis when compression is on. `noeviction` would then apply to your customer data. The Data Plane logs a warning at startup when the two defaults share an endpoint.\n\nFor how to size the Gateway database and the Redis versions it needs, see [Redis databases](https://redis.io/docs/latest/operate/iris/context-gateway/self-managed/prerequisites#redis-databases)."
    },
    {
      "id": "context-surface-databases",
      "title": "Context Surface databases",
      "role": "content",
      "text": "A Context Surface stores only its database ID. Give each plane its own Redis user on the database, as listed in [Redis users for Surface databases](https://redis.io/docs/latest/operate/iris/context-gateway/self-managed/prerequisites#redis-users-for-surface-databases). The `databaseConnections` registry is shared by both planes, so supply each plane's URL for that database ID through an overlay. Put the Control Plane's login in a `controlplane.overlays` Secret and the Data Plane's login in a `dataplane.overlays` Secret. Overlays load last and override the registry's connection settings:\n\n[code example]\n\nReference each Secret from `controlplane.overlays` or `dataplane.overlays` with `existingSecret` and `secretKey`, as in the embedding example below. To change a password, update the Secret and restart both planes.\n\nSemantic and hybrid search tools embed the agent's text before they search. Configure an OpenAI-compatible embedding service in a mounted Data Plane overlay. Save a restricted local file named `embedding-secret.yaml`:\n\n[code example]\n\nOmit `api_key` if your provider does not require it. Create a Secret from that file:\n\n[code example]\n\nReference it in your Helm values:\n\n[code example]\n\nKeep the real key out of `gateway-values.yaml` and source control. The overlay contains application settings under `context_surface`, without a Helm `dataplane.config` wrapper.\n\nWithout an embedding service, every other tool still works, and semantic or hybrid search calls fail with `embedding_unavailable`. The embedding client ignores proxy variables and dials the endpoint directly, and with an API key set every endpoint must use HTTPS.\n\nThe Data Plane keeps at most two clients for each Surface database. One Surface database sees at most `2 × client.pool_size × Data Plane replicas` connections, so set `context_surface.client_pool.client.pool_size` explicitly, in a Data Plane overlay like the embedding one."
    },
    {
      "id": "cache-and-compression",
      "title": "Cache and compression",
      "role": "content",
      "text": "Both features are off by default. Turn them on in the Data Plane configuration:\n\n[code example]\n\n| Setting | Default | Meaning |\n| --- | --- | --- |\n| `cache.max_entry_bytes` | `262144` | The largest result the cache stores, in bytes. |\n| `cache.max_memory_share` | `0.4` | Fraction of Gateway database `maxmemory` available to cache entries. |\n| `compression.max_memory_share` | `0.5` | Fraction of Gateway database `maxmemory` available to handles. |\n| `compression.ttl` | `15m` | How long a handle lives. At most `24h`. A tool policy can override it. |\n| `compression.inline_max_bytes` | `32768` | Results above this size become handles. |\n| `compression.max_capture_bytes` | `8388608` | Maximum captured result size in bytes. Automatic capture can return oversized results inline. Explicit capture fails if no handle can be stored. |\n| `compression.preview_items`, `compression.preview_bytes` | `3`, `2048` | The size of a handle stub's preview. |\n| `compression.max_surface_items` | `10000` | Maximum records collected for a Surface handle. |\n| `compression.max_paged_capture_bytes_in_flight` | `67108864` | Per-process byte budget for Surface pages being collected across captures. |\n| `compression.max_concurrent_paged_captures` | `4` | Maximum captures collecting Surface continuation pages at once per process. |\n| `compression.paged_capture_wait` | `2s` | Maximum wait for a paging slot. |\n| `compression.paged_capture_time_budget` | `2s` | Total paging budget, including the wait for a slot. The request deadline can shorten it. |\n\nThese settings turn each feature on for the deployment. Each Gateway then has its own `cache` and `compression` settings, and a feature serves a Gateway only when both the deployment and the Gateway turn it on. Both need the Gateway to have a database, from its `databaseId` or from [the default Gateway database](#default-databases).\n\nThe cache still needs a policy for each tool. Compression applies to every eligible result on a Gateway that has it on, unless a tool's policy opts out. See [Turn the cache and compression on or off](https://redis.io/docs/latest/develop/ai/context-engine/context-gateway/manage-gateways#turn-the-cache-and-compression-on-or-off).\n\nSurface filter and text queries can collect continuation pages into a handle. Capture stops at the row, byte, time, or concurrency limit and returns a partial handle when it has a storable partial result. Read its warning before treating its count as the complete query count. Semantic and hybrid top-K queries are not continued. See [Compression](https://redis.io/docs/latest/develop/ai/context-engine/context-gateway/concepts#compression)."
    },
    {
      "id": "bound-data-plane-memory",
      "title": "Bound Data Plane memory",
      "role": "content",
      "text": "Set `dataplane.resources.limits.memory` for each pod. At startup, `dataplane.config.memory_limit_ratio` sets the Go runtime's soft memory limit to that fraction of the container limit. The default is `0.7`. An explicit `GOMEMLIMIT` environment value takes precedence. Without a container memory limit, the automatic setting does not apply. This soft limit leaves room for memory outside the Go heap and does not replace the container limit."
    },
    {
      "id": "cache-metadata-resolutions",
      "title": "Cache metadata resolutions",
      "role": "content",
      "text": "The metadata resolution cache is off by default. Enable it only after every Control Plane instance supports invalidating Gateway resolutions after Upstream mutation attempts. Roll out all writers first. Credentials and caller permissions are still checked on every invocation.\n\n[code example]\n\n`max_age` defaults to `30s` and must be positive. It bounds stale resolutions when invalidation fails. Keep this configuration under `dataplane.config`, not `shared.config`."
    },
    {
      "id": "trust-a-private-ca",
      "title": "Trust a private CA",
      "role": "content",
      "text": "Set `tls.caBundle` to a PEM bundle, or mount a single certificate under `/etc/ssl/certs` with the chart's `examples/certificates.yaml`. Restart both planes after you rotate a certificate."
    },
    {
      "id": "configure-security-and-tracing",
      "title": "Configure security and tracing",
      "role": "configuration",
      "text": "`security.profile: fips` runs the Federal Information Processing Standards (FIPS)-oriented profile. Both planes then reject plaintext Redis URLs and disabled certificate verification, including in the registered databases.\n\nBoth planes serve Prometheus metrics at `/metrics`. Set `shared.config.opentelemetry.enable_tracing: true` and an OpenTelemetry Protocol (OTLP) endpoint for traces."
    },
    {
      "id": "scale-and-schedule-the-planes",
      "title": "Scale and schedule the planes",
      "role": "content",
      "text": "Each plane starts one replica. Set `replicaCount`, or turn on `autoscaling`, with `resources`, `nodeSelector`, `tolerations`, and `affinity` under `controlplane` and `dataplane`. Autoscaling is off by default, needs Metrics Server, and needs `resources.requests.cpu` or `resources.requests.memory` for each target it uses. Leave headroom for old and new pods during upgrades.\n\nSet `podDisruptionBudget.enabled: true` to give each plane that keeps more than one pod a PodDisruptionBudget with `maxUnavailable: 1`. `dataplane.strategy` sets the Data Plane Deployment's rollout strategy.\n\nThe `startupProbe`, `livenessProbe`, and `readinessProbe` of each plane use `/health/liveness` and `/health/readiness` by default, and accept native Kubernetes probe fields. Set a probe to `null` to turn it off. The default startup probe allows 150 seconds before liveness checks begin."
    }
  ],
  "examples": [
    {
      "id": "database-registry-ex0",
      "language": "yaml",
      "code": "shared:\n  config:\n    databases:\n      clinics: {name: Clinic directory}\n      results: {name: Gateway database}\ndatabaseConnections:\n  - existingSecret: gateway-database-connections\n    secretKey: config.yaml",
      "section_id": "database-registry"
    },
    {
      "id": "default-databases-ex0",
      "language": "yaml",
      "code": "shared:\n  config:\n    databases:\n      gateway-db: {name: Gateway database}\n      surface-db: {name: Surface database}\ndatabaseConnections:\n  - existingSecret: gateway-database-connections\n    secretKey: config.yaml\ndefaultDatabases:\n  gatewayDb: gateway-db\n  surfaceDb: surface-db",
      "section_id": "default-databases"
    },
    {
      "id": "context-surface-databases-ex0",
      "language": "yaml",
      "code": "databases:\n  clinics:\n    urls:\n      - rediss://<plane-user>:<password>@<host>:<port>",
      "section_id": "context-surface-databases"
    },
    {
      "id": "context-surface-databases-ex1",
      "language": "yaml",
      "code": "context_surface:\n  embedding:\n    endpoint: https://api.openai.com/v1\n    model: text-embedding-3-small\n    api_key: <embedding-provider-api-key>",
      "section_id": "context-surface-databases"
    },
    {
      "id": "context-surface-databases-ex2",
      "language": "bash",
      "code": "kubectl -n <namespace-name> create secret generic gateway-embedding --from-file=config.yaml=embedding-secret.yaml",
      "section_id": "context-surface-databases"
    },
    {
      "id": "context-surface-databases-ex3",
      "language": "yaml",
      "code": "dataplane:\n  overlays:\n    - existingSecret: gateway-embedding\n      secretKey: config.yaml",
      "section_id": "context-surface-databases"
    },
    {
      "id": "cache-and-compression-ex0",
      "language": "yaml",
      "code": "dataplane:\n  config:\n    cache:\n      enabled: true\n    compression:\n      enabled: true\n      ttl: 15m",
      "section_id": "cache-and-compression"
    },
    {
      "id": "cache-metadata-resolutions-ex0",
      "language": "yaml",
      "code": "dataplane:\n  config:\n    metadata_cache:\n      resolution:\n        enabled: true\n        max_age: 30s",
      "section_id": "cache-metadata-resolutions"
    }
  ]
}
