# Configure self-managed Context Gateway

```json metadata
{
  "schema_version": 2,
  "title": "Configure self-managed Context Gateway",
  "description": "Prepare Helm values, file references, credential storage, the database registry, Context Surface databases, and optional features for Context Gateway.",
  "categories": ["docs","operate","iris"],
  "tableOfContents": {"sections":[{"id":"helm-values","title":"Helm values"},{"id":"file-references","title":"File references"},{"id":"credential-storage","title":"Credential storage"},{"id":"database-registry","title":"Database registry"},{"children":[{"id":"gateway-database-requirements","title":"Gateway database requirements"}],"id":"default-databases","title":"Default databases"},{"id":"context-surface-databases","title":"Context Surface databases"},{"id":"cache-and-compression","title":"Cache and compression"},{"id":"bound-data-plane-memory","title":"Bound Data Plane memory"},{"id":"cache-metadata-resolutions","title":"Cache metadata resolutions"},{"id":"trust-a-private-ca","title":"Trust a private CA"},{"id":"configure-security-and-tracing","title":"Configure security and tracing"},{"id":"scale-and-schedule-the-planes","title":"Scale and schedule the planes"}]}

,
  "codeExamples": []
}
```


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).

## Helm values

| Value | Purpose |
| --- | --- |
| `tenantId` | The deployment's tenant. Must equal the Identity Service's single tenant ID. |
| `publicBaseURL` | The Data Plane's public origin, used in every Gateway's `mcpEndpoint`. |
| `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. |
| `identityService.baseURL` | The Identity Service runtime API that checks agent keys. |
| `identityService.allowInsecureTransport` | `true` for an in-cluster `http://` Identity Service URL. Default `false`. |
| `profile` | `prod` (default) or `dev` logging. |
| `security.profile` | `standard` (default) or `fips`. See [Configure security and tracing](#configure-security-and-tracing). |
| `imagePullSecrets` | Credentials for a private registry. |
| `controlplane.config`, `dataplane.config` | Application settings for each plane. They default to `{}`, so the image's defaults apply. |

Start 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.

## File references

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.

| Helm reference | File contents | Used by |
| --- | --- | --- |
| `metadataDB` | YAML with `metadata_db.urls`. | Both planes |
| `credentialStorage.redis.connection` | YAML with `credential_storage.redis.urls`. | Both planes |
| `credentialStorage.redis.keyring` | Directory of base64-encoded 32-byte Advanced Encryption Standard (AES) keys. | Both planes |
| `credentialStorage.vault.credentials.controlplane` | YAML with `credential_storage.vault.token`, with write and delete access. | Control Plane |
| `credentialStorage.vault.credentials.dataplane` | YAML with `credential_storage.vault.token`, with read access. | Data Plane |
| `databaseConnections[]` | YAML with `databases.<id>.urls`. | Both planes |
| `controlplane.adminToken` | The admin token operators send to the Control Plane. | Control Plane |
| `controlplane.internalToken` | The token Identity Service sends to the grant callback. | Control Plane |
| `dataplane.runtimeToken` | The token the Data Plane sends to Identity Service. | Data Plane |
| `license` | The license file as issued. | Both planes |
| `tls.caBundle` | A PEM-encoded certificate authority (CA) bundle. | Both planes |

The 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.

## Credential storage

| `credentialStorage.provider` | Configuration |
| --- | --- |
| `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. |
| `vault` | Separate writer and reader tokens for the Control Plane and Data Plane. |
| `aws_secrets_manager` | Separate Identity and Access Management (IAM) roles for each plane through workload identity. |

## Database registry

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.

```yaml
shared:
  config:
    databases:
      clinics: {name: Clinic directory}
      results: {name: Gateway database}
databaseConnections:
  - existingSecret: gateway-database-connections
    secretKey: config.yaml
```

Both 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.

## Default databases

An on-prem deployment uses two databases from the registry, and each has its own job:

| Database | Holds | Written by |
| --- | --- | --- |
| Gateway database | Cached results and compression handles. | Only the Gateway. |
| Surface database | The customer data that Context Surfaces read. | Never the Gateway. |

Name a default for each so that administrators do not repeat the ID on every request. Both defaults are optional, and both planes read them:

```yaml
shared:
  config:
    databases:
      gateway-db: {name: Gateway database}
      surface-db: {name: Surface database}
databaseConnections:
  - existingSecret: gateway-database-connections
    secretKey: config.yaml
defaultDatabases:
  gatewayDb: gateway-db
  surfaceDb: surface-db
```

The 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.

| Value | Used when | Without it |
| --- | --- | --- |
| `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. |
| `defaultDatabases.surfaceDb` | A Context Surface create sends no `contextSurface.databaseId`. An explicit ID always wins. | That create returns `400` on `contextSurface.databaseId`. |

Redis Cloud has no defaults and always requires the database ID.

### Gateway database requirements

The Gateway database should use `maxmemory-policy noeviction`, and compression also needs the JSON module. The checks differ by where they run:

- 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.
- 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`.
- 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.

Size 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.

The 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.

By 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.

When 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).

Do 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.

For 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).

## Context Surface databases

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:

```yaml
databases:
  clinics:
    urls:
      - rediss://<plane-user>:<password>@<host>:<port>
```

Reference 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.

Semantic 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`:

```yaml
context_surface:
  embedding:
    endpoint: https://api.openai.com/v1
    model: text-embedding-3-small
    api_key: <embedding-provider-api-key>
```

Omit `api_key` if your provider does not require it. Create a Secret from that file:

```bash
kubectl -n <namespace-name> create secret generic gateway-embedding --from-file=config.yaml=embedding-secret.yaml
```

Reference it in your Helm values:

```yaml
dataplane:
  overlays:
    - existingSecret: gateway-embedding
      secretKey: config.yaml
```

Keep 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.

Without 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.

The 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.

## Cache and compression

Both features are off by default. Turn them on in the Data Plane configuration:

```yaml
dataplane:
  config:
    cache:
      enabled: true
    compression:
      enabled: true
      ttl: 15m
```

| Setting | Default | Meaning |
| --- | --- | --- |
| `cache.max_entry_bytes` | `262144` | The largest result the cache stores, in bytes. |
| `cache.max_memory_share` | `0.4` | Fraction of Gateway database `maxmemory` available to cache entries. |
| `compression.max_memory_share` | `0.5` | Fraction of Gateway database `maxmemory` available to handles. |
| `compression.ttl` | `15m` | How long a handle lives. At most `24h`. A tool policy can override it. |
| `compression.inline_max_bytes` | `32768` | Results above this size become handles. |
| `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. |
| `compression.preview_items`, `compression.preview_bytes` | `3`, `2048` | The size of a handle stub's preview. |
| `compression.max_surface_items` | `10000` | Maximum records collected for a Surface handle. |
| `compression.max_paged_capture_bytes_in_flight` | `67108864` | Per-process byte budget for Surface pages being collected across captures. |
| `compression.max_concurrent_paged_captures` | `4` | Maximum captures collecting Surface continuation pages at once per process. |
| `compression.paged_capture_wait` | `2s` | Maximum wait for a paging slot. |
| `compression.paged_capture_time_budget` | `2s` | Total paging budget, including the wait for a slot. The request deadline can shorten it. |

These 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).

The 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).

Surface 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).

## Bound Data Plane memory

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.

## Cache metadata resolutions

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.

```yaml
dataplane:
  config:
    metadata_cache:
      resolution:
        enabled: true
        max_age: 30s
```

`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`.

## Trust a private CA

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.

## Configure security and tracing

`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.

Both planes serve Prometheus metrics at `/metrics`. Set `shared.config.opentelemetry.enable_tracing: true` and an OpenTelemetry Protocol (OTLP) endpoint for traces.

## Scale and schedule the planes

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.

Set `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.

The `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.



