> 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/terraform-eks.md).

# Amazon EKS with Terraform

Provision an EKS cluster, a managed database and an OpenIAM deployment on AWS with Terraform — the recommended route for a new AWS environment.

**This is the recommended route when you are creating a new AWS environment.** Terraform provisions the network, the EKS cluster, the managed database and every OpenIAM release in one operation.

If the cluster already exists, use Helm instead — [*Kubernetes overview*](/installation/kubernetes.md).

## What this builds, and how long it takes

|                |                                                                                          |
| -------------- | ---------------------------------------------------------------------------------------- |
| **You get**    | A VPC, an EKS cluster, an RDS instance and a complete OpenIAM deployment, in one region  |
| **Time**       | 2–3 hours, most of it waiting on AWS                                                     |
| **Hosts**      | A workstation with Terraform, Helm, `kubectl` and the AWS CLI                            |
| **Downtime**   | None — this creates new infrastructure                                                   |
| **Reversible** | Mostly. `terraform destroy` leaves some resources behind — see [*Remove it*](#remove-it) |

**Scope.** One OpenIAM deployment, with one instance of each component, in a single zone. See [*Scope and availability*](/installation/scope-and-availability.md).

## Before you start

**On your workstation**

| Tool       | Version                                              |
| ---------- | ---------------------------------------------------- |
| Terraform  | **1.13.4 or later.** The modules declare `>= 1.13.4` |
| Helm       | 3                                                    |
| `kubectl`  | Matching the cluster version you deploy              |
| AWS CLI    | v2, configured with `aws configure`                  |
| `git`      | Any                                                  |
| `envsubst` | From `gettext`. The configuration generator needs it |

**In AWS**

* Permission to create a VPC, an EKS cluster, IAM roles and policies, RDS instances and load balancers.
* **The Elasticsearch service-linked role**, which is created once per AWS organisation. If your account has never had one, create it before the first apply.

**From OpenIAM**

* The deployment materials repository, and the exact revision issued for this release.
* Registry credentials for `registry.openiam.com`.

## Set these first

{% code overflow="wrap" %}

```bash
export NS=<the namespace you will deploy into>
```

{% endcode %}

***

## Build it

### 1. Get the deployment materials

{% code overflow="wrap" %}

```bash
git clone <the repository URL from your account team>
cd kubernetes-docker-configuration
git checkout <the revision issued for this release>
```

{% endcode %}

### 2. Set your registry credentials

{% code overflow="wrap" %}

```bash
export DOCKER_REGISTRY=registry.openiam.com
export DOCKERHUB_USERNAME=<your registry username>
export DOCKERHUB_PASSWORD=<your registry password>
```

{% endcode %}

**These are read by the configuration generator in step 5.** Set them in the shell you will run the rest of the procedure in.

### 3. Select AWS in `main.tf`

The provider is chosen by one line. Open `main.tf` and set the deployment module's source:

{% code overflow="wrap" %}

```hcl
module "deployment" {
  source = "./modules/core/aws"
```

{% endcode %}

**Do this before running anything else.** The configuration generator reads this line to decide how to prepare storage.

### 4. Set your values

Three files carry settings.

**`env.sh`** — set `APP_NAME`, `OPENIAM_VERSION_NUMBER`, `BUILD_ENVIRONMENT` and `CLOUD_PROVIDER=aws`.

**`terraform.tfvars`** — the main input file. `app_name` **must equal** `APP_NAME` in `env.sh`.

Settings you must change:

| Setting                                      | Shipped                 | Set it to                                                                                                                                                                                                              |
| -------------------------------------------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `app_name`                                   | a sample value          | Your deployment name. Used for the cluster, the VPC, the database instance and every release                                                                                                                           |
| `app_version`                                |                         | The OpenIAM release you are deploying                                                                                                                                                                                  |
| `region`                                     | `us-west-2`             | Your region                                                                                                                                                                                                            |
| `replica_count`                              | `3`                     | **This is the worker node count, not a pod count.** `2` is a reasonable minimum for a simple deployment; see [*Prepare the cluster*](/installation/kubernetes/prepare-the-cluster.md) for the capacity it must provide |
| `namespace`                                  | `default`               | A namespace of your own. Nothing prevents `default`, but it gives you no isolation, quota or policy boundary                                                                                                           |
| `kubernetes.aws.eks_version`                 | an older version        | The version you intend to run. `m4.xlarge` is the smallest tested `machine_type`                                                                                                                                       |
| `cluster.aws.cluster_endpoint_public_access` | `true`                  | **`false`, with `cluster_endpoint_private_access = true`**, unless you need the API server on the internet                                                                                                             |
| `database.type` and `database.aws.engine`    | inconsistent as shipped | **Make these agree.** For PostgreSQL: `type = "Postgres"`, `aws.engine = "postgres"`, `aws.port = "5432"`. For MariaDB: `type = "MariaDB"`, `aws.engine = "mariadb"`, `aws.port = "3306"`                              |
| `database.aws.backup_retention_period`       | `0`                     | **A non-zero value for anything but a throwaway build.** `0` disables automated backups                                                                                                                                |
| `database.aws.multi_az`                      | `false`                 | Leave `false` — this release covers single-zone deployments                                                                                                                                                            |
| `rproxy.aws.certificateManagerARN`           | empty                   | Your ACM certificate ARN, if terminating TLS at the load balancer                                                                                                                                                      |
| `rproxy.https.disabled`                      | `1`                     | Leave `1` when terminating TLS at the load balancer. See [*TLS*](#tls)                                                                                                                                                 |

**Every credential in `terraform.tfvars` ships as a placeholder, and every one of them must be changed before you apply:**

* the database root account and all eight schema accounts,
* the cache password,
* the search tier's username and password,
* the secret store's key password and its four keystore passwords,
* the message broker's password and cluster cookie,
* the graph storage password,
* the dashboard password, if you enable monitoring.

**These are the live credentials for the life of the deployment.** They also end up in Terraform state — see [*State and secrets*](#state-and-secrets).

**`iam.auto.tfvars`** — additional IAM users and roles to add to the cluster's access configuration. If you leave it as shipped, **only the identity that created the cluster can reach it.** Add the roles your team uses before you apply, or you will be adding them afterwards through a single privileged session.

### 5. Generate the configuration

{% code overflow="wrap" %}

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

{% endcode %}

**Choose the storage option for a managed cluster.** For EKS, the bundled provider is the supported choice unless you have prepared your own — [*Prepare the cluster*](/installation/kubernetes/prepare-the-cluster.md) explains what `ReadWriteMany` storage the deployment needs and why.

**What it does:** renders the Helm values into `.deploy/` from `env.sh` and `terraform.tfvars`, and generates the search tier's certificates on first run.

**This step is required and it comes before `terraform init`.** Terraform reads the rendered files at plan time, so a plan without them fails.

{% code overflow="wrap" %}

```bash
ls .deploy/ | head
```

{% endcode %}

**Expected:** one values file per component.

### 6. Initialise and review the plan

{% code overflow="wrap" %}

```bash
terraform init
terraform plan
```

{% endcode %}

**Read the plan before applying.** It is the only review point before AWS resources are created, and it is where a wrong region, an inconsistent database setting or a missing credential shows up cheaply.

### 7. Apply

{% code overflow="wrap" %}

```bash
terraform apply
```

{% endcode %}

**How long:** 2–3 hours. The EKS cluster alone takes 20–30 minutes, the database another 10–20, and the OpenIAM releases follow.

**You may need to run `terraform apply` a second time.** The first run can complete before the cluster's credentials are available to the Kubernetes and Helm providers. A second apply is safe and is the documented behaviour.

### 8. Point `kubectl` at the cluster

The apply updates `~/.kube/config`.

{% code overflow="wrap" %}

```bash
kubectl get nodes
```

{% endcode %}

**Expected:** your worker nodes, `Ready`.

### 9. Expose the deployment and set DNS

{% code overflow="wrap" %}

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

{% endcode %}

The reverse proxy's service is the entry point. Expose it with a load balancer service or an ingress according to your convention, then create a DNS record pointing at it.

| Setting                       | Value                                  |
| ----------------------------- | -------------------------------------- |
| **Health check path**         | `/idp/login`                           |
| **Backend protocol and port** | HTTP, the reverse proxy service's port |

**Check `/idp/login`, not `/`** — it exercises the proxy and a real application behind it.

### 10. Verify

[*Verify a Kubernetes deployment*](/installation/kubernetes/verify.md), then [*First sign-in*](/installation/rpm/rpm-single-node/first-sign-in.md) using the DNS name from step 9 as the content provider's domain pattern.

***

## Verify it

Before moving on, confirm the apply produced what it should:

{% code overflow="wrap" %}

```bash
terraform output
kubectl get nodes
kubectl get pods -n "$NS" --no-headers | awk '{print $3}' | sort | uniq -c
kubectl get pvc -n "$NS"
```

{% endcode %}

**Expected:** outputs present, nodes `Ready`, pods `Running` or `Completed`, every claim `Bound`.

Then work through [*Verify a Kubernetes deployment*](/installation/kubernetes/verify.md) in full — it checks each tier rather than each object.

***

## TLS

Two supported arrangements:

| Where TLS terminates                                  | Set                                                                                                     |
| ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| **At the AWS load balancer**, with an ACM certificate | `rproxy.aws.certificateManagerARN` to the ARN, and `rproxy.https.disabled = "1"`                        |
| **In the cluster**, at the reverse proxy              | `rproxy.https.disabled = "0"`, and supply your certificate and key in `.ssl/` before running `setup.sh` |

**Terminating at the load balancer is the simpler arrangement** and is what most deployments use.

## State and secrets

**State is local.** The modules configure no remote backend, so `terraform.tfstate` is a file in your working directory.

Two consequences to plan for:

* **The state file contains the credentials you set in `terraform.tfvars`**, including the database root password and the secret store's keystore passwords. Treat it as a secret: keep it out of version control, store it where your organisation keeps credentials, and restrict access to it.
* **Losing the state file means Terraform can no longer manage the deployment.** Back it up with the same care as the deployment itself, and keep it with the matching `terraform.tfvars`, `env.sh`, `main.tf` and `.deploy/` — a later apply or destroy needs all of them.

Configuring a remote backend with locking and encryption is a reasonable improvement and is a standard Terraform exercise; nothing in the modules prevents it.

## Re-running

`terraform apply` is safe to re-run and is how you apply a configuration change. Run `terraform plan` first and read it — an apply that proposes to replace the database or the cluster is telling you a value was changed that cannot be changed in place.

## Remove it

{% code overflow="wrap" %}

```bash
terraform destroy
```

{% endcode %}

**Destroy does not complete cleanly on AWS**, and this is documented behaviour rather than a fault in your deployment. Expect to:

* remove several modules from state by hand with `terraform state rm` before the destroy will proceed,
* delete the load balancers manually,
* delete the VPC manually, because it reports a dependency violation while the load balancers exist.

**Work through it in that order — load balancers, then VPC** — and confirm in the console that nothing remains before you consider the environment removed.

**If you set a non-zero database backup retention**, destroy fails on the option group, which is associated with a snapshot that cannot be deleted. Remove the snapshot first, or set retention to `0` before destroying a throwaway environment.

## If it goes wrong

| What you see                                              | What it means                                                         | What to do                                                                               |
| --------------------------------------------------------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| `terraform plan` fails on a missing file under `.deploy/` | `setup.sh` has not been run, or was run before your values were set   | Set the values, run `./setup.sh`, plan again                                             |
| The plan shows no AWS resources                           | `main.tf` still points at another module                              | Step 3                                                                                   |
| A duplicate IAM policy error on apply                     | A policy from a previous deployment with the same `app_name` survives | Remove it, or choose a different `app_name`                                              |
| Apply completes but the Kubernetes resources are missing  | The providers had no cluster credentials on the first pass            | Run `terraform apply` again                                                              |
| Only you can reach the cluster                            | `iam.auto.tfvars` was left as shipped                                 | Add your team's roles and apply again                                                    |
| Volume claims stay `Pending`                              | The storage class or the CSI driver                                   | [*Prepare the cluster*](/installation/kubernetes/prepare-the-cluster.md)                 |
| The search tier restarts repeatedly                       | `vm.max_map_count` on the worker nodes                                | [*Amazon EKS*](/installation/kubernetes/amazon-eks.md)                                   |
| Pods cannot pull images                                   | Registry credentials were not exported before `setup.sh`              | Export them and re-run `setup.sh`, then apply                                            |
| A pod cannot resolve any DNS name                         | This can follow a force-deleted pod on EKS                            | **Never delete an OpenIAM pod with `--force`.** Delete it normally and let it reschedule |

## What this procedure does not cover

**The AWS network layout is fixed by the module.** The VPC CIDR and its subnets are not exposed as variables, so bringing your own VPC or changing the address ranges means changing the module. If your organisation requires a specific network design, plan for that before choosing this route.

Multi-zone placement, running more than one instance of a component, and disaster-recovery design are covered in the [Solution Architecture and Design Guide](/solution-architecture/deployment/availability-and-recovery.md) — [*Scope and availability*](/installation/scope-and-availability.md).

## Related

* [*Prepare the cluster*](/installation/kubernetes/prepare-the-cluster.md) — what the deployment needs from any cluster
* [*Amazon EKS*](/installation/kubernetes/amazon-eks.md) — node settings and storage detail
* [*Verify a Kubernetes deployment*](/installation/kubernetes/verify.md) — the next page
* [*Managed databases*](/installation/rpm/cloud/managed-databases.md) — the engine parameters

*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/terraform-eks.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.
