{
  "schema_version": 2,
  "id": "operate/iris/intent-router/self-managed/configuration",
  "title": "Configure self-managed Intent Router",
  "url": "https://redis.io/docs/latest/operate/iris/intent-router/self-managed/configuration/",
  "summary": "Set the Helm values, storage Secret, embedding models, customer databases, search tuning, and limits for Redis Intent Router.",
  "tags": [
    "docs",
    "operate",
    "iris"
  ],
  "last_updated": "2026-11-04T01:09:56+02:00",
  "page_type": "content",
  "content_hash": "59b14aa243e45b5f2c58806bec9868e84d81326d4bd3bcace88f56aaa2cca2b5",
  "sections": [
    {
      "id": "overview",
      "title": "Overview",
      "role": "overview",
      "text": "This page describes how to configure self-managed Intent Router before you [deploy the chart](https://redis.io/docs/latest/operate/iris/intent-router/self-managed/deploy). You prepare a Helm values file and the Secrets it references. The values file sets your tenant, image tags, and tokens. The Secrets hold what shouldn't be in a values file: Redis connection URLs, embedding provider credentials, and the license.\n\nEvery install needs the Helm values, the storage Secret, and an embedding model, covered in the first three sections. The remaining sections are optional, and their defaults work for a first deployment. Use them to store Routers in customer databases, tune search and matching, adjust Router lifecycle timing, trust a private certificate authority, enable Federal Information Processing Standards (FIPS) mode or tracing, or scale the planes."
    },
    {
      "id": "helm-values",
      "title": "Helm values",
      "role": "content",
      "text": "Set these values for every install:\n\n| Value | Meaning |\n| --- | --- |\n| `tenantId` | The one tenant this installation serves. Required. Agent-key grants name it as `tenant`. |\n| `storage.existingSecret` | The Secret that holds the Redis connection overlays. Required. |\n| `license.existingSecret` | The Secret that holds the license. Required. |\n| `inference.models` | The embedding model the deployment offers. Required. |\n| `controlplane.image`, `dataplane.image` | An explicit `tag` for each image. The repositories default to `redislabs/iris-intent-router-control` and `redislabs/iris-intent-router-data`. |\n| `controlplane.adminToken`, `controlplane.internalToken` | The control plane's tokens. |\n| `dataplane.identityServiceBaseURL` | The URL of your Identity Service release. The data plane checks agent keys there. Required. |\n| `identityService.credential` | The data plane's runtime credential, registered on the Identity Service release. Required. |\n| `identityService.allowInsecureTransport` | `true` to accept an `http://` Identity Service URL. Default `false`. |\n\nA minimal values file looks like this:\n\n[code example]\n\nTo let the chart generate a token instead, delete its `existingSecret` line. See [Credentials](https://redis.io/docs/latest/operate/iris/intent-router/self-managed/authentication#credentials).\n\nThe `identityService` block holds only `allowInsecureTransport` and `credential`. The chart deploys no Identity Service. An in-cluster Identity Service Service speaks plain Hypertext Transfer Protocol (HTTP), and the chart rejects an `http://` Identity Service URL by default. Rendering fails until you choose: set `dataplane.identityServiceBaseURL` to an `https://` endpoint, or set `identityService.allowInsecureTransport=true` to accept cleartext introspection on the pod network. The FIPS profile refuses `allowInsecureTransport`."
    },
    {
      "id": "storage-secret",
      "title": "Storage Secret",
      "role": "content",
      "text": "One Secret holds a YAML overlay for each service, merged over the rendered configuration. Save each overlay as a file named after its Secret key:\n\n| Secret key | Value that names it | Content |\n| --- | --- | --- |\n| `intent-controlplane-storage.yaml` | `storage.controlplaneKey` | `storage.metadata.urls`: Router records. |\n| `intent-dataplane-storage.yaml` | `storage.dataplaneKey` | `storage.redis.urls`: intents, examples, and the per-Router vector indexes. `runtime.redis.urls`: the mailbox Redis, which is the control plane's metadata Redis. |\n\n`intent-controlplane-storage.yaml`:\n\n[code example]\n\n`intent-dataplane-storage.yaml`:\n\n[code example]\n\nThe Identity Service's metadata Redis belongs to the Identity Service release, not to this Secret.\n\nIn the mailbox Redis, the data plane reads and writes only the mailbox keys under `storage.keyPrefix`. Nothing else of the control plane's data is touched. Keep real URLs and passwords in the Secret, not in values files."
    },
    {
      "id": "embedding-models",
      "title": "Embedding models",
      "role": "content",
      "text": "`inference.models` lists the deployment's one embedding model as a public, immutable identity: `provider`, `model`, and `dimensions`. The control plane serves it from `GET /iris/v1/intent/models` as an `EMBEDDING` model that needs no caller credential, and refuses to create a Router with any other vector space.\n\nFor any provider other than `noop`, create the inference Secret named by `inference.existingSecret`. It holds one key, `inference.yaml`, with an `embedders_connection_details` entry for each provider. The entry's name must match `inference.models[].provider`. It carries the provider's `base_url`, `credentials`, and an optional `protocol`, but no model or dimensions:\n\n[code example]\n\n[code example]\n\nThe `noop` provider needs no Secret. It is a local, word-based embedder that matches by shared words, not meaning, and is for development only. The chart refuses it unless you set `inference.allowLocalModels=true`."
    },
    {
      "id": "customer-databases",
      "title": "Customer databases",
      "role": "content",
      "text": "To let Routers live in a customer's own Redis, list the databases under `databases:` in both the control-plane and data-plane overlay keys, with the same IDs and the same URLs. An ID has 1 to 32 letters, digits, or hyphens, and isn't case-sensitive.\n\n[code example]\n\nA Router then names one with `databaseId` when it is created, and never changes it. The control plane only checks the ID and never connects. Each database needs Search, and none may be the mailbox Redis. Changing a URL needs a restart: a changed Secret checksum rolls the pods. Set `controlplane.requireDatabase=true` to make every Router name a database.\n\nA Router whose database is unreachable keeps waiting with the reason, then fails at the provisioning deadline. A serving Router answers `503` with `Retry-After` until the database returns."
    },
    {
      "id": "search-and-match-tuning",
      "title": "Search and match tuning",
      "role": "content",
      "text": "Tune `dataplane.search` for index recall and memory: `hnsw.m`, `hnsw.ef_construction`, `hnsw.ef_runtime`, `query_timeout`, `ready_cache_ttl`, and `verify_interval`. The Hierarchical Navigable Small World (HNSW) values shape recall and memory, never correctness.\n\n`dataplane.limits` and `dataplane.match` are copied verbatim, with snake_case keys, into the data-plane configuration. Set fractional values such as `dataplane.match.default_threshold` from a values file, because `--set` turns `0.8` into a string. Each key in this table sits under `dataplane.`:\n\n| Key | Default | Meaning |\n| --- | --- | --- |\n| `limits.max_intents_per_router` | 1000 | Intents a Router can hold. |\n| `limits.max_intents_per_batch` | 100 | Intents in one bulk request. |\n| `limits.max_examples_per_batch` | 5000 | Examples across one bulk request. |\n| `limits.max_examples_per_add` | 256 | Examples in one add-examples request. It can't usefully exceed 256, the per-intent ceiling. |\n| `match.scan_k` | 50 | Nearest examples read before grouping by intent. Between 1 and 1000. |\n| `match.aggregation` | `min` | How an intent's example distances combine: `min` scores an intent as its closest example, `avg` as the average of its examples among the nearest. |\n| `match.default_max_intents` | 5 | Intents a match returns when the request sets no `limit`. |\n| `match.max_intents_cap` | 50 | The largest `limit` a match accepts. |\n| `match.default_threshold` | 0.7 | The minimum score for a Router that has none. Every Router created through the control plane carries its own `minScore`, 0.7 unless set, so this rarely applies. |"
    },
    {
      "id": "router-lifecycle",
      "title": "Router lifecycle",
      "role": "content",
      "text": "| Value | Default | Meaning |\n| --- | --- | --- |\n| `storage.maxRoutersPerTenant` | 128 | Routers the tenant can hold. Creating more fails with `409`. |\n| `controlplane.reconcile.interval` | `5s` | How often the control plane reconciles Routers with the data plane's status. |\n| `controlplane.reconcile.provisioningDeadline` | `10m` | How long a new Router can stay `PROVISIONING` before it fails. |\n| `dataplane.pollInterval` | `2s` | How often the data plane reads the mailbox for Router changes. |\n| `dataplane.startupBudget` | `20s` | How long the data plane's first convergence pass can run before it opens its listener. At most `40s`, because the liveness probe restarts the pod 50 seconds after start. Routers the pass doesn't reach are picked up by later passes. |\n\nA Router change reaches the data plane within about one poll interval."
    },
    {
      "id": "trust-a-private-certificate-authority",
      "title": "Trust a private certificate authority",
      "role": "content",
      "text": "Set `tls.caCertSecret` to a Secret that holds additional certificate authority (CA) certificates. Every container mounts it at `/etc/ssl/custom`, and `SSL_CERT_DIR` points there. Set `tls.caCertSecretChecksum` so the pods roll when the Secret changes."
    },
    {
      "id": "fips-and-tracing",
      "title": "FIPS and tracing",
      "role": "content",
      "text": "Set `security.profile: fips` to select the FIPS runtime. Rendering rejects a FIPS release unless `dataplane.identityServiceBaseURL` is an explicit HTTP Secure (HTTPS) endpoint, and refuses `identityService.allowInsecureTransport`. Set the FIPS profile and an `https://` grant-validation URL on the Identity Service release too. Use `rediss://` URLs in the storage Secret, including `runtime.redis.urls` and any customer databases. Rendering can't check them, because they live in the Secret, and the data plane refuses to start in the FIPS profile if any of them isn't `rediss://`.\n\nSet `tracing.enabled` and `tracing.otlpGRPCEndpoint` to send spans to your OpenTelemetry collector over the OpenTelemetry Protocol (OTLP). The exporter uses plaintext gRPC, so use a trusted local hop."
    },
    {
      "id": "scale-and-schedule-the-planes",
      "title": "Scale and schedule the planes",
      "role": "content",
      "text": "The control plane and data plane scale independently. Set `replicaCount`, `resources`, `nodeSelector`, `tolerations`, and `affinity` for each in the chart values. Scale the Identity Service in its own release. The data plane carries the matching load, so scale it first.\n\nEach plane runs one replica by default. Set `podDisruptionBudget.enabled=true` to add a PodDisruptionBudget with `maxUnavailable: 1` for each plane that runs more than one replica. `dataplane.strategy` sets the data-plane Deployment's rollout strategy, for example `RollingUpdate` with `maxUnavailable: 0` and `maxSurge: 1`."
    }
  ],
  "examples": [
    {
      "id": "helm-values-ex0",
      "language": "yaml",
      "code": "tenantId: <tenant-id>\n\ncontrolplane:\n  image:\n    repository: redislabs/iris-intent-router-control\n    tag: <tag>\n  adminToken:\n    existingSecret: intent-controlplane-admin\n  internalToken:\n    existingSecret: intent-controlplane-internal\n\ndataplane:\n  image:\n    repository: redislabs/iris-intent-router-data\n    tag: <tag>\n  identityServiceBaseURL: http://ids-iris-identity-service:9200\n\nidentityService:\n  allowInsecureTransport: true\n  credential:\n    existingSecret: intent-dp-credential\n\nlicense:\n  existingSecret: intent-license\n\nstorage:\n  existingSecret: intent-storage\n\ninference:\n  existingSecret: intent-inference\n  models:\n    - provider: openai\n      model: text-embedding-3-small\n      dimensions: 1536",
      "section_id": "helm-values"
    },
    {
      "id": "storage-secret-ex0",
      "language": "yaml",
      "code": "storage:\n  metadata:\n    urls:\n      - rediss://<metadata-user>:<metadata-password>@<metadata-host>:6379",
      "section_id": "storage-secret"
    },
    {
      "id": "storage-secret-ex1",
      "language": "yaml",
      "code": "storage:\n  redis:\n    urls:\n      - rediss://<dataplane-user>:<dataplane-password>@<dataplane-host>:6379\nruntime:\n  redis:\n    urls:\n      - rediss://<metadata-user>:<metadata-password>@<metadata-host>:6379",
      "section_id": "storage-secret"
    },
    {
      "id": "embedding-models-ex0",
      "language": "yaml",
      "code": "embedders_connection_details:\n  openai:\n    base_url: https://api.openai.com/v1\n    credentials:\n      type: static\n      api_key: <openai-api-key>",
      "section_id": "embedding-models"
    },
    {
      "id": "embedding-models-ex1",
      "language": "bash",
      "code": "kubectl -n <namespace-name> create secret generic intent-inference --from-file=inference.yaml",
      "section_id": "embedding-models"
    },
    {
      "id": "customer-databases-ex0",
      "language": "yaml",
      "code": "databases:\n  <database-id>:\n    urls: [\"rediss://:<password>@<customer-host>:6379\"]",
      "section_id": "customer-databases"
    }
  ]
}
