> For the complete documentation index, see [llms.txt](https://docs.openiam.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.openiam.com/installation/kubernetes/deploy-with-helm.md).

# Deploy with Helm

Deploy OpenIAM into a prepared Kubernetes cluster with Helm — settings, credentials, storage, the deployment run, the published entry point and safe re-runs.

This is the complete procedure for deploying OpenIAM into a cluster that already exists — an EKS or AKS cluster you run, or any conformant Kubernetes cluster. Everything OpenIAM needs is a Helm chart, and a deployment is a set of Helm releases in one namespace.

For a **new** AWS or Azure environment, [*EKS with Terraform*](/installation/kubernetes/terraform-eks.md) and [*AKS with Terraform*](/installation/kubernetes/terraform-aks.md) create the network, the cluster and the managed database as well, and are the shorter path. Use this page when the cluster and the database are already yours.

## What this builds, and how long it takes

|                |                                                                                                                                                                              |
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **You get**    | A complete OpenIAM deployment — around thirty pods across the application services, the six interfaces, the infrastructure tiers, the interface router and the reverse proxy |
| **Time**       | 2–3 hours, most of it waiting for pods to settle                                                                                                                             |
| **Hosts**      | One deployment machine with `kubectl` and Helm, and your cluster                                                                                                             |
| **Downtime**   | None                                                                                                                                                                         |
| **Reversible** | Yes — the deployment is a set of Helm releases in one namespace, and can be removed                                                                                          |

## Before you start

**Done already**

* [*Prepare the cluster*](/installation/kubernetes/prepare-the-cluster.md), including the `ReadWriteMany` storage test and `vm.max_map_count` on the nodes. Every requirement on that page applies here unchanged.
* Your provider's page, if you are on one: [*Amazon EKS*](/installation/kubernetes/amazon-eks.md) or [*Azure AKS*](/installation/kubernetes/azure-aks.md).
* **A decision on the database.** In this release the Helm route deploys the bundled in-cluster database and creates its own accounts — see step 4. A deployment on your own database server takes a Terraform route. [*Managed databases*](/installation/rpm/cloud/managed-databases.md) covers the engines and their parameters.

**Have ready**

* **Registry credentials** for OpenIAM's container registry, issued with your entitlement.
* **The deployment materials for this release**, and the exact revision to use. Your OpenIAM account team issues both together. **Use that revision and no other** — the values files, the chart versions and the image tags are matched to the release, and a different revision will not deploy it.
* **A deployment machine** with `kubectl` configured against the cluster, **Helm 3**, `git`, `openssl`, and `envsubst` (from the `gettext` package, which is often not installed by default). Ubuntu 22.04 is the tested choice. On a private cluster this machine must sit inside the network the API server is reachable from.

**Decide**

* **The namespace.** One deployment per namespace. `openiam` is the default.
* **The deployment name.** It prefixes every Helm release and appears in service names, so choose something you are content to see everywhere. It cannot be changed after deployment without redeploying.
* **Where the database lives**, and therefore which route you are on. The bundled in-cluster database is what this route deploys in this release. It has no availability arrangement and no backup regime of its own, so plan its backup deliberately — [*Back up and restore*](/installation/kubernetes/back-up-and-restore.md). A deployment on your own database server takes a Terraform route.
* **How TLS is terminated** — at your load balancer or ingress, or by the reverse proxy itself. Step 9 covers both.

## Set these first

{% code overflow="wrap" %}

```bash
export NS=<the namespace for this deployment>
export APP=<the deployment name>
export OPENIAM_VERSION=<the release you are installing>
export OPENIAM_URL=<the address users will type>
export DB_HOST=<your database endpoint>
export DB_PORT=<3306 for MariaDB or MySQL, 5432 for PostgreSQL>
export RWX_CLASS=<the storage class that supports ReadWriteMany>
```

{% endcode %}

***

## Build it

### 1. Prepare the deployment machine

{% code overflow="wrap" %}

```bash
sudo mkdir -p /usr/local/openiam && cd /usr/local/openiam
git clone <the deployment materials repository> kubernetes-docker-configuration
cd kubernetes-docker-configuration
git checkout <the revision issued for this release>
```

{% endcode %}

Confirm the tools are present:

{% code overflow="wrap" %}

```bash
kubectl version -o json | jq -r '.serverVersion.gitVersion'
helm version --short
which envsubst openssl git
```

{% endcode %}

**Expected:** the cluster's version, Helm 3.x, and a path for each command. If `envsubst` is missing, install `gettext` — the values files are rendered with it and nothing works without it.

### 2. Create the namespace

{% code overflow="wrap" %}

```bash
kubectl create namespace "$NS"
kubectl config set-context --current --namespace="$NS"
```

{% endcode %}

The deployment creates the namespace if it is absent, but setting it as your context's default now means every check on this page and on [*Verify a Kubernetes deployment*](/installation/kubernetes/verify.md) looks in the right place.

### 3. Set the deployment settings

Three files carry settings, and **they must agree with each other**.

**`env.sh`** — the values the Helm values files are rendered from:

| Setting                  | Set it to                                                                              |
| ------------------------ | -------------------------------------------------------------------------------------- |
| `APP_NAME`               | Your deployment name                                                                   |
| `BUILD_ENVIRONMENT`      | `prod`                                                                                 |
| `OPENIAM_VERSION_NUMBER` | The release you are installing                                                         |
| `APPLICATION_DOMAIN`     | The address users will reach the platform on                                           |
| `CLOUD_PROVIDER`         | `aws`, `azure`, or `other` for a cluster you run                                       |
| `TIME_ZONE`              | The deployment's time zone, in `Region/City` form                                      |
| `DOCKER_REGISTRY`        | OpenIAM's registry, or your own if you are mirroring — see *Restricted networks* below |

**`terraform.tfvars`** — set `app_name` to **the same value as `APP_NAME`**. This route does not run Terraform, but the settings script reads the file to confirm the two are in step, and waits until they match:

{% code overflow="wrap" %}

```bash
grep app_name terraform.tfvars
```

{% endcode %}

**`setup-no-tf.sh`** — set the same three values at the top of the file, and the namespace:

{% code overflow="wrap" %}

```bash
BUILD_ENVIRONMENT="prod"
OPENIAM_VERSION_NUMBER="<the release you are installing>"
APP_NAME="<your deployment name>"
NAMESPACE="<your namespace>"
```

{% endcode %}

**These must match `env.sh` exactly.** The values files are rendered from `env.sh` and the deployment passes its own settings on the command line; if the two disagree, the image tags in the values files disagree with the ones actually deployed.

Two more, in the same block:

| Setting            | Set it to                                                         |
| ------------------ | ----------------------------------------------------------------- |
| `NUM_WORKER_NODES` | `1` — the replica count for each component in a simple deployment |
| `FLYWAY_COMMAND`   | `migrate`                                                         |

### 4. Point the deployment at your database

The database settings sit together near the top of `setup-no-tf.sh`:

{% code overflow="wrap" %}

```bash
DEPLOY_IN_CLUSTER_DATABASE="true"
DB_HOST="<your database endpoint>"
DB_PORT="3306"
DB_TYPE="MariaDB"
DB_DIALECT="org.hibernate.dialect.MySQLDialect"
```

{% endcode %}

**Which database this route uses in this release**

| Database                            | This route                                                                                                                                                                                                                                                                    |
| ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **The bundled in-cluster database** | `DEPLOY_IN_CLUSTER_DATABASE="true"`. The deployment creates the databases and the eight accounts itself. **This is the supported arrangement for the Helm route in this release**, and it is suitable for evaluation and for non-production work                              |
| **Your own database server**        | Use [*EKS with Terraform*](/installation/kubernetes/terraform-eks.md) or [*AKS with Terraform*](/installation/kubernetes/terraform-aks.md), which carry the account provisioning. On AWS the Terraform route creates the schemas and accounts on the managed instance for you |

**Why the distinction exists.** The deployment generates the credentials for all eight schema accounts itself and stores them in a Kubernetes secret — step 7. On the bundled database it then creates the accounts using those generated credentials. Against a database server you already run, the accounts have to be created with those same generated credentials before the schema migration runs, and the Helm route does not yet carry that step. The Terraform route does.

**If your deployment must use your own database server**, take a Terraform route, or talk to your OpenIAM account team about the sequence for your environment. Pointing this route at an external server and letting it run will fail at the schema migration.

[*Managed databases*](/installation/rpm/cloud/managed-databases.md) covers the engines, the parameters that must be set and the connection limits, and applies whichever route provisions the accounts.

### 5. Render the values files

{% code overflow="wrap" %}

```bash
./setup.sh
```

{% endcode %}

**What it does:** renders one Helm values file per component into `.deploy/` from the settings in `env.sh`, and generates the search tier's internal certificates if they are not already there.

**It asks how storage is provided:**

| Choice                           | When to take it                                                                                                                           |
| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| **Use openiam nfs pvc provider** | You have no `ReadWriteMany` class of your own and want the bundled provider. Suitable for evaluation                                      |
| **Use longhorn pvc provider**    | You run Longhorn                                                                                                                          |
| **Use own storage class**        | You prepared one — EFS on EKS, Azure Files on AKS, NFS or a distributed filesystem on your own cluster. **This is the production choice** |

Choosing your own class asks for its name and writes it into the volume claims. Confirm it took:

{% code overflow="wrap" %}

```bash
grep -c "storageClass: '$RWX_CLASS'" .deploy/openiam.pvc.values.yaml
```

{% endcode %}

**Expected:** a count greater than zero, and no remaining claims asking for a class you do not have:

{% code overflow="wrap" %}

```bash
grep "storageClass:" .deploy/openiam.pvc.values.yaml | sort | uniq -c
```

{% endcode %}

**What you should see overall:** `.deploy/` populated with one values file per component.

{% code overflow="wrap" %}

```bash
ls .deploy/ | wc -l
```

{% endcode %}

### 6. Deploy

**Run it inside a terminal multiplexer.** The run is long and a dropped session ends it partway through:

{% code overflow="wrap" %}

```bash
sudo apt-get install -y screen
screen -S openiam-deploy
./setup-no-tf.sh
```

{% endcode %}

Detach with **Ctrl+A** then **D**, and reattach with `screen -r openiam-deploy`.

**It asks for your registry credentials** the first time. They are stored in the cluster as an image pull secret and are not asked for again on a re-run.

**Then it asks which storage provider to use** — take the same choice you took in step 5.

**What it deploys, in order:**

| Order | Release           | What it is                                                                          |
| ----: | ----------------- | ----------------------------------------------------------------------------------- |
|     1 | `$APP-configmap`  | The configuration and the generated credentials. Everything else reads from it      |
|     2 | `$APP-redis`      | The cache, with Sentinel                                                            |
|     3 | `cassandra`       | Storage for the graph tier                                                          |
|     4 | `$APP-opensearch` | The search tier                                                                     |
|     5 | `$APP-gremlin`    | The graph engine                                                                    |
|     6 | `$APP-rabbitmq`   | The message broker                                                                  |
|     7 | `$APP-database`   | The bundled database, and the eight accounts the deployment created credentials for |
|     8 | `$APP-pvc`        | The shared volume claims                                                            |
|     9 | `$APP-consul`     | The secret store's backend                                                          |
|    10 | `$APP-vault`      | The secret store                                                                    |
|    11 | 24 releases       | The application services, the six interfaces and the two message-broker connectors  |
|    12 | `$APP-nginx`      | The interface router                                                                |
|    13 | `$APP-rproxy`     | The reverse proxy — the entry point users reach                                     |

**The configuration, database, secret-store backend and interface router releases wait for readiness before the run continues.** The rest are created back to back. That is deliberate: components restart while the tier beneath them comes up, so `Init`, `PodInitializing` and `CrashLoopBackOff` in the first fifteen minutes are the deployment settling rather than a fault.

**If a release that waits times out, the run stops there.** That is the signal to look at that release rather than let the rest pile up behind it.

**How long:** 30–45 minutes for the releases to be created, then up to another 30 for every pod to reach `Running` and `Ready`.

### 7. Find the generated credentials

**The deployment generates every internal credential itself** — for the cache, the broker, the search tier, the secret store and the eight database accounts — and stores them in a Kubernetes secret named `secrets` in your namespace. Nothing is printed to the terminal and nothing is written to a file on the deployment machine.

To list which credentials exist:

{% code overflow="wrap" %}

```bash
kubectl get secret secrets -n "$NS" -o jsonpath='{.data}' | jq -r 'keys[]'
```

{% endcode %}

To read one, by name:

{% code overflow="wrap" %}

```bash
kubectl get secret secrets -n "$NS" -o jsonpath='{.data.<key>}' | base64 --decode
```

{% endcode %}

**Two things follow from this, and both matter:**

* **Back up this secret with the rest of the deployment.** Without it the database accounts, the secret store and the search tier cannot be reconstructed.
* **Removing the configuration release regenerates every one of them.** Step 11 covers what that means for a re-run.

The secret store also produces unseal keys and a root token during its own bootstrap. Record those from the secret-store bootstrap job's output and store them where your organisation keeps break-glass credentials.

### 8. Watch it settle

{% code overflow="wrap" %}

```bash
kubectl get pods -n "$NS" -w
```

{% endcode %}

**Expected, once settled:** every pod `Running`, and the `READY` column showing all containers ready. Jobs show `Completed` — that is correct, not a failure.

Give it the full 30 minutes before treating a restarting pod as a problem. If something is still restarting after that, [*Troubleshoot*](/installation/kubernetes/troubleshoot.md) maps the symptom to a cause.

### 9. Publish the entry point

**Users reach the platform through the reverse proxy**, which is the only component that should be exposed. The interface router and the services behind it stay inside the cluster.

{% code overflow="wrap" %}

```bash
kubectl get svc -n "$NS" | grep rproxy
```

{% endcode %}

**The proxy hands requests to the interface router at `$APP-nginx:8080`, and to the service bus at `$APP-esb:9080`.** The deployment sets both. This is the same arrangement as an RPM deployment, described in [*Configure the reverse proxy*](/installation/rpm/rpm-single-node/configure-the-reverse-proxy.md).

Expose it in whichever way your cluster does this:

| Approach                  | What to do                                                                                                                                                                                                 |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Load balancer service** | Change the proxy service's type to `LoadBalancer`. Your provider's page has the annotations — [*Amazon EKS*](/installation/kubernetes/amazon-eks.md), [*Azure AKS*](/installation/kubernetes/azure-aks.md) |
| **Ingress**               | Point an ingress rule at the proxy service on its HTTP port                                                                                                                                                |

**Health check path: `/idp/login`.** Configure your load balancer or ingress to use it rather than `/`.

**TLS.** Two supported arrangements:

* **Terminate at the load balancer or ingress** — the common enterprise choice. The proxy serves HTTP inside the cluster and your entry point presents the certificate. Nothing further is needed in the deployment.
* **Terminate at the reverse proxy.** Place `openiam.crt` and `openiam.key` in the `.ssl` directory of the deployment materials **before step 6**, together with `openiam.sslchain.crt` or `openiam.ssl.ca.crt` if your certificate needs them. Then set `openiam.http=0` on the reverse-proxy release in `setup-no-tf.sh`. The certificate files are carried into the cluster by the configuration release, so adding them afterwards means re-running that release.

**DNS.** Point the name in `OPENIAM_URL` at the load balancer or ingress address, and confirm it resolves before the first sign-in:

{% code overflow="wrap" %}

```bash
nslookup "$OPENIAM_URL"
curl -s -o /dev/null -w '%{http_code}\n' "https://$OPENIAM_URL/idp/login"
```

{% endcode %}

**Expected:** a resolved address, and `200` or a `30x` redirect.

**The address must be settled before first sign-in.** It is recorded in the platform's configuration at that point, and changing it afterwards means editing the content provider.

### 10. Verify, then sign in

Go to [*Verify a Kubernetes deployment*](/installation/kubernetes/verify.md) — it is a genuine check that the deployment works, not a repeat of `kubectl get pods`.

Then [*First sign-in*](/installation/rpm/rpm-single-node/first-sign-in.md) to set the administrator password and record the published address. **On Kubernetes, two of its answers differ:**

| Field                         | Value on Kubernetes                                                                                                   |
| ----------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| **Application supports SSL?** | `Yes` if users reach the platform over HTTPS, whether TLS is terminated at your load balancer or at the reverse proxy |
| **Application Servers**       | `http://$APP-nginx:8080` — the interface router's service, using your deployment name                                 |

***

## Verify it

One pass over the whole deployment:

{% code overflow="wrap" %}

```bash
echo "== releases";  helm list -n "$NS"
echo "== pods";      kubectl get pods -n "$NS" --field-selector=status.phase!=Running
echo "== claims";    kubectl get pvc -n "$NS"
echo "== proxy";     kubectl get svc -n "$NS" -l app.kubernetes.io/instance="$APP-rproxy"
echo "== entry";     curl -s -o /dev/null -w '%{http_code}\n' "https://$OPENIAM_URL/idp/login"
```

{% endcode %}

**Expected:** every release `deployed`; no pods listed by the second command other than `Completed` jobs; every claim `Bound`; a proxy service with an external address; and `200` or a `30x` from the entry point.

## If it goes wrong

| What you see                                               | What it means                                                                                                                                   | What to do                                                                                                                                                                                                |
| ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Waiting for app_name in terraform.tfvars to equal <name>` | The deployment name does not match in all three files                                                                                           | Set it the same in `env.sh`, `setup-no-tf.sh` and `terraform.tfvars`                                                                                                                                      |
| `envsubst: command not found`                              | The `gettext` package is not installed                                                                                                          | Install it, then re-run `./setup.sh`                                                                                                                                                                      |
| `.deploy/` is empty, or a values file is missing           | `setup.sh` has not been run, or ran before the settings were set                                                                                | Set the settings, then run `./setup.sh` again                                                                                                                                                             |
| Volume claims stay `Pending`                               | No default storage class, or no `ReadWriteMany` class                                                                                           | [*Prepare the cluster*](/installation/kubernetes/prepare-the-cluster.md), step 3                                                                                                                          |
| Claims ask for a class named `nfs` that does not exist     | The storage choice in `setup.sh` was not the one you meant                                                                                      | Re-run `./setup.sh`, choose **Use own storage class**, and give the class name                                                                                                                            |
| The search tier restarts repeatedly                        | `vm.max_map_count` is below 262144 on the nodes                                                                                                 | Set it in the node pool or launch template and roll the nodes                                                                                                                                             |
| Images will not pull                                       | The registry credentials, or no egress from the cluster                                                                                         | Check the credentials, then the cluster's outbound rules                                                                                                                                                  |
| `flyway-job` retries without completing                    | The database is unreachable, its parameters are not set, a schema is missing, or the route was pointed at your own database server — see step 4 | Read its log with `kubectl logs job/flyway-job -n "$NS"`. The job retries rather than failing fast, so the log is the signal, not the job's status. Fix the cause, then delete the job by name and re-run |
| Pods schedule and then evict                               | The cluster is short of capacity                                                                                                                | [*Prepare the cluster*](/installation/kubernetes/prepare-the-cluster.md), step 2                                                                                                                          |
| Pods are healthy but the console does not load             | The proxy is not reaching the interface router                                                                                                  | Confirm `$APP-nginx` exists and the proxy points at it — step 9                                                                                                                                           |
| The secret store will not unseal                           | The secret store's backend is not ready                                                                                                         | Check the `$APP-consul` release before the `$APP-vault` release                                                                                                                                           |

[*Troubleshoot*](/installation/kubernetes/troubleshoot.md) covers each of these in more depth.

## Run it again safely

**The deployment script is written to be re-run.** Every release is an upgrade-or-install, so a second run is intended to reconcile what is there rather than duplicate it. This is how a settings change is applied: edit the setting, run `./setup.sh` if it lives in `env.sh`, then run `./setup-no-tf.sh` again.

**Take a backup before re-running a deployment that holds data you need** — [*Back up and restore*](/installation/kubernetes/back-up-and-restore.md). A re-run touches the credentials every component authenticates with, and a backup is the difference between a bad re-run being an inconvenience and being an incident.

**The configuration release is the exception.** It is created once and then skipped, because re-creating it generates a new set of credentials that the already-running components do not have. If you do need to rebuild it, expect to redeploy everything that reads from it.

**To restart a deployment that failed partway through**, rather than repairing it:

{% code overflow="wrap" %}

```bash
kubectl delete job flyway-job -n "$NS"
./setup-no-tf.sh
```

{% endcode %}

Deleting only the migration job lets it run again; the rest of the deployment is reconciled in place. **Delete that job by name.** Deleting every job in the namespace also removes the secret-store bootstrap and the broker's keystore generator, which do not re-create themselves on a re-run.

## Upgrade to a new release

Upgrading is the same deployment run against a newer version, so the mechanics are the ones above. What differs is what has to be in place first, and that depends on how far you are moving: [*Upgrading OpenIAM*](/installation/kubernetes/kubernetes-upgrade.md) settles whether yours is a minor upgrade or a major one and takes it from there.

{% hint style="danger" %}
**Back up the database and the `secrets` secret first.** The schema migration runs as part of the deployment and is not reversible by re-running the previous version.
{% endhint %}

## Remove a deployment

{% code overflow="wrap" %}

```bash
APP_NAME="$APP" NAMESPACE="$NS" ./teardown.sh
```

{% endcode %}

It lists what it will remove and asks you to type the namespace to confirm. **It deletes the persistent volume claims**, and with them the data in the cache, the broker, the search tier, the graph store and — if you used it — the in-cluster database. **Take a backup first.**

The namespace itself is left in place for you to inspect and remove.

## Back up and restore

A Kubernetes deployment holds state in five places, and a restore needs all five: the eight database schemas, the `secrets` secret, the secret store's data, its unseal keys and root token, and the deployment configuration. **A managed database backup on its own is not a backup of the deployment.**

[*Back up and restore*](/installation/kubernetes/back-up-and-restore.md) has the procedure and the commands for each, and the restore rehearsal that turns it into a recovery time you can quote.

**Take the first backup before the deployment carries anything you would mind losing** — which in practice means before the first synchronisation run.

## Restricted networks

For a cluster with no outbound internet access, mirror the images into your own registry and point the deployment at it.

**Get the exact image list for your release**, from a machine that can reach OpenIAM's registry, after step 5:

{% code overflow="wrap" %}

```bash
helm repo add openiam <the chart repository for your release channel>
helm repo update
for f in .deploy/*.values.yaml; do
  c=$(basename "$f" .values.yaml)
  helm template x "openiam/$c" --version "$OPENIAM_VERSION" -f "$f" 2>/dev/null
done | grep -oE 'image: *"?[^"]+' | sed 's/image: *"\?//' | sort -u > openiam-images.txt
wc -l openiam-images.txt
```

{% endcode %}

Every image sits under two namespaces in OpenIAM's registry — one for the infrastructure tiers and one for the application services — and all carry the release tag. Mirror both:

{% code overflow="wrap" %}

```bash
while read -r img; do
  docker pull "$img"
  docker tag  "$img" "<your-registry>/${img#*/}"
  docker push "<your-registry>/${img#*/}"
done < openiam-images.txt
```

{% endcode %}

Then set `DOCKER_REGISTRY` in `env.sh` to your registry, and re-run `./setup.sh` before deploying.

**The Helm charts are pulled from OpenIAM's chart repository too.** For a fully disconnected deployment, pull each chart with `helm pull` on a connected machine and host them where the cluster can reach them.

**Pin the images you use for diagnostics as well.** The checks on [*Prepare the cluster*](/installation/kubernetes/prepare-the-cluster.md) and [*Troubleshoot*](/installation/kubernetes/troubleshoot.md) use a small utility image; mirror one at a fixed tag and use that, rather than pulling `latest` from a public registry.

## What this procedure does not cover

Running more than one replica of each component, monitoring and log aggregation, your cluster's own policies, and cluster creation. Sizing and topology are in the [Solution Architecture and Design Guide](/solution-architecture/solution-architecture.md). [*Scope and availability*](/installation/scope-and-availability.md) states what this shape protects against.

## Related

* [*Prepare the cluster*](/installation/kubernetes/prepare-the-cluster.md) — the prerequisites this page assumes
* [*Verify a Kubernetes deployment*](/installation/kubernetes/verify.md) — the next page, and not optional
* [*First sign-in*](/installation/rpm/rpm-single-node/first-sign-in.md) — with the two Kubernetes answers in step 10
* [*Managed databases*](/installation/rpm/cloud/managed-databases.md)
* [*Troubleshoot*](/installation/kubernetes/troubleshoot.md)

*Applies to OpenIAM 2026.8.1.*


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.openiam.com/installation/kubernetes/deploy-with-helm.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
