# Terraform example: AWS three-cluster mesh

Generated reference for the AWS example that provisions the three-cluster mesh. For the guided setup, see [Provision infrastructure](/deploy/provision-infra).

### Requirements

| Name | Version |
| ---- | ------- |
| terraform | >= 1.14 |
| aws | ~> 6.0 |
| helm | ~> 3.0 |
| kubernetes | ~> 3.0 |
| local | ~> 2.0 |
| tls | ~> 4.0 |

### Providers

| Name | Version |
| ---- | ------- |
| aws.hub | 6.54.0 |
| aws.spoke\_0 | 6.54.0 |
| aws.spoke\_1 | 6.54.0 |
| local | 2.9.0 |
| tls | 4.3.0 |

### Modules

| Name | Source | Version |
| ---- | ------ | ------- |
| cluster\_hub | ../../ | n/a |
| cluster\_spoke\_0 | ../../ | n/a |
| cluster\_spoke\_1 | ../../ | n/a |
| ks\_privatelink | ../../privatelink-aws | n/a |
| ks\_privatelink\_spoke\_1 | ../../privatelink-aws | n/a |
| nats\_privatelink | ../../privatelink-aws | n/a |
| nats\_privatelink\_spoke\_1 | ../../privatelink-aws | n/a |
| pol\_reader\_privatelink | ../../privatelink-aws | n/a |
| pol\_reader\_privatelink\_spoke\_1 | ../../privatelink-aws | n/a |
| pol\_writer\_privatelink | ../../privatelink-aws | n/a |
| pol\_writer\_privatelink\_spoke\_1 | ../../privatelink-aws | n/a |
| relay\_privatelink | ../../privatelink-aws | n/a |
| relay\_privatelink\_spoke\_1 | ../../privatelink-aws | n/a |

### Resources

| Name | Type |
| ---- | ---- |
| [aws\_route53\_record.orchestrator](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record) | resource |
| [aws\_route53\_record.ui](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record) | resource |
| [aws\_security\_group.hub\_spoke\_endpoint](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource |
| [aws\_security\_group.spoke\_0\_nats\_endpoint](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource |
| [aws\_security\_group.spoke\_0\_relay\_endpoint](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource |
| [aws\_security\_group.spoke\_1\_nats\_endpoint](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource |
| [aws\_security\_group.spoke\_1\_relay\_endpoint](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource |
| [local\_sensitive\_file.chart\_values](https://registry.terraform.io/providers/hashicorp/local/latest/docs/resources/sensitive_file) | resource |
| [local\_sensitive\_file.extra\_values](https://registry.terraform.io/providers/hashicorp/local/latest/docs/resources/sensitive_file) | resource |
| [local\_sensitive\_file.kubeconfig](https://registry.terraform.io/providers/hashicorp/local/latest/docs/resources/sensitive_file) | resource |
| [tls\_private\_key.external\_rule\_signing](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/resources/private_key) | resource |
| [tls\_private\_key.imported\_key\_encryptor](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/resources/private_key) | resource |
| [aws\_acm\_certificate.orchestrator](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/acm_certificate) | data source |
| [aws\_acm\_certificate.ui](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/acm_certificate) | data source |
| [aws\_route53\_zone.ui](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/route53_zone) | data source |

### Inputs

| Name | Description | Type | Default | Required |
| ---- | ----------- | ---- | ------- | :------: |
| artifactory\_password | JFrog Artifactory password / access token. | `string` | n/a | yes |
| artifactory\_username | JFrog Artifactory username (used by both clusters to pull chart images). | `string` | n/a | yes |
| aws\_profile | Base AWS CLI profile used by all three clusters unless overridden per-cluster below. For a single-account PoC, set just this. | `string` | n/a | yes |
| mpc\_key\_stores | MPC shard identities, one per cluster. The deployment is hard-pinned to 3-of-3, so this must have exactly 3 entries. Required (no default) — set it in the gitignored terraform.tfvars. The MPC keypair is self-generated by each keystore on first boot, not configured here. | <pre>list(object(\{<br />    name                     = string<br />    backupEncryptorPublicKey = string<br />    importedKeyEncryptorPem  = optional(string, "")<br />  }))</pre> | n/a | yes |
| policy\_servers | Policy-server identities, one per cluster (3 in the 3-of-3 deployment). Required (no default) — set it in the gitignored terraform.tfvars. The reader/writer identity keypairs are self-generated on first boot and propagated via the cluster-identity set — nothing key-related is configured here. | <pre>list(object(\{<br />    writerName = string<br />    readerName = string<br />  }))</pre> | n/a | yes |
| relay\_api\_key | Relay API key (both clusters use the same key). | `string` | n/a | yes |
| app\_image\_pull\_policy | imagePullPolicy for the orchestrator/policy-server/vault-ui containers. null (default) keeps the chart default (IfNotPresent, right for pinned tags); set "Always" when using a mutable tag like the dev-registry latest so restarts re-pull instead of serving a stale cached layer. | `string` | `null` | no |
| app\_image\_registry | Registry host+namespace for the orchestrator/policy-server/vault-ui images, e.g. your own registry mirror. null (default) keeps the sodot-docker-oci production registry. | `string` | `null` | no |
| app\_image\_tag | Tag for the orchestrator/policy-server/vault-ui images when app\_image\_registry is set (e.g. "latest" for a main-branch dev build). Ignored when app\_image\_registry is null (the tag then follows image\_tags). | `string` | `"latest"` | no |
| aws\_profile\_hub | Override the AWS CLI profile for the hub cluster (shard 1, runs ORC/NATS/UI). null = use aws\_profile. | `string` | `null` | no |
| aws\_profile\_spoke\_0 | Override the AWS CLI profile for spoke-0 (shard 0). null = use aws\_profile. | `string` | `null` | no |
| aws\_profile\_spoke\_1 | Override the AWS CLI profile for spoke-1 (shard 2). null = use aws\_profile. Give this its own account for the one-shard-per-account trust boundary. | `string` | `null` | no |
| aws\_region | Base AWS region used by all three clusters unless overridden per-cluster below. | `string` | `"us-east-1"` | no |
| aws\_region\_hub | Override the region for the hub cluster. null = use aws\_region. | `string` | `null` | no |
| aws\_region\_spoke\_0 | Override the region for spoke-0. null = use aws\_region. | `string` | `null` | no |
| aws\_region\_spoke\_1 | Override the region for spoke-1. null = use aws\_region. Set a different region to exercise the cross-region PrivateLink path — the wiring adapts automatically. | `string` | `null` | no |
| chart\_ha | When true (default), the whole stack runs HA: orchestrator/policy-server/UI at 2 replicas (with HPAs), the (unified) MPC keystore at 2 replicas, clustered (3-node RAFT) NATS, relay HPA min 2, PodDisruptionBudgets on, and the Nitro/general node-group floors sized to fit (2 Nitro nodes/shard for the 2-replica keystore, 1 general). When false, main.tf passes extra\_helm\_values that collapse EVERY workload — orchestrator, policy-server, UI, keystore, relay — to a single replica, disable the HPAs, use non-clustered NATS, turn PDBs off, and drop the node floors to 1 Nitro / 1 general — the cheapest footprint. NOTE: a single orchestrator is a hard signing ceiling versus 2 active-active replicas; that throughput hit is the accepted tradeoff for cheap mode. terraform.tfvars sets false. | `bool` | `true` | no |
| chart\_oci\_registry | OCI registry (incl. oci:// scheme, no chart name) the operator should install the crypto-vault chart from, e.g. "oci://repo.sodot.dev/sodot-helm-charts". Drives the helm\_handoff output's chart hint only. null (default, the in-repo harness) points at the local chart dir instead. | `string` | `null` | no |
| chart\_version | crypto-vault chart version the operator should install when chart\_oci\_registry is set (the release version this module was published with). Drives the helm\_handoff output's chart hint only. Ignored when chart\_oci\_registry is null. | `string` | `null` | no |
| db\_deletion\_protection | Pass through to the cluster module's RDS deletion\_protection. Defaults to true (production-safe: RDS is guarded against accidental destroy, matching the module default). Set false in terraform.tfvars for a throwaway PoC / smoke test where painless teardown matters. | `bool` | `true` | no |
| db\_force\_ssl | Pass through to the cluster module's RDS TLS enforcement (rds.force\_ssl=1 + app DSN sslmode=require). The module itself defaults this to true; this example defaults to false because the orchestrator/policy-server/mpc-key-store binaries may be built without a SQLx TLS backend, so requiring TLS CrashLoops them. Flip to true (and drop this override) once those images ship TLS support. | `bool` | `false` | no |
| db\_instance\_type | RDS instance class for each cluster's PostgreSQL. Defaults to db.t3.small (the module default). terraform.tfvars can raise it (e.g. db.t3.large) to give the orchestrator's per-request DB row-lock path (SELECT ... FOR UPDATE) more CPU/IOPS headroom under higher request concurrency. (orchestrator.maxConnections caps in-flight requests, not the DB pool, so this is about lock-path throughput, not connection count.) | `string` | `"db.t3.small"` | no |
| db\_multi\_az | Enable RDS Multi-AZ failover on every cluster. Defaults to true (HA). terraform.tfvars sets false for a cheaper, faster-teardown single-AZ smoke test. | `bool` | `true` | no |
| db\_skip\_final\_snapshot | Pass through to the cluster module's RDS skip\_final\_snapshot. Defaults to false (production-safe: a final snapshot is taken on destroy). Set true for a throwaway PoC / CI smoke test so repeated teardowns don't pile up orphaned final-\* snapshots (storage cost). | `bool` | `false` | no |
| deployment\_name | Prefix used to name resources in each account (e.g. "crypto-vault"). | `string` | `"crypto-vault"` | no |
| eks\_endpoint\_public\_access | Expose each cluster's EKS API server publicly. The building-block module defaults this to FALSE (private-only, production-safe); this example defaults it to TRUE so you can `terraform apply` from your laptop. Set false for a private-only test too (then run Terraform from inside the VPC / a peered network / VPN). | `bool` | `true` | no |
| eks\_endpoint\_public\_access\_cidrs | EKS public-endpoint allowlist — CIDRs allowed to reach each cluster's kube-apiserver when eks\_endpoint\_public\_access = true. NOTE: defaults to UNRESTRICTED (\["0.0.0.0/0"]) for first-run convenience (the endpoint is still IAM-gated). There is NO current-IP data lookup — TIGHTEN this to your operator/CI egress CIDRs before any non-test use. | `list(string)` | <pre>\[<br />  "0.0.0.0/0"<br />]</pre> | no |
| eks\_node\_instance\_types | EC2 instance type(s) for the Nitro keystore node group, applied identically to all three shards (signing throughput is gated by the slowest party, so they must match). The enclave vCPU carve-out auto-derives from this — (cores - 1) \* threads\_per\_core — so a bigger type directly raises the enclave-CPU-bound signing ceiling. Must be Nitro-Enclave-capable (non-burstable, non-metal, >=4 vCPU). Defaults to c5.xlarge (2 enclave vCPU); terraform.tfvars can set a larger type (e.g. c7i.2xlarge, 6 enclave vCPU) for higher throughput. | `list(string)` | <pre>\[<br />  "c5.xlarge"<br />]</pre> | no |
| enclave\_image\_registry | Registry host+namespace for the enclave-infra images (enclave-runner/enclaver/enclave-resource-server). null (default) keeps the sodot-docker-oci production registry. | `string` | `null` | no |
| enclave\_image\_tag | Tag for the enclave-infra images. Defaults to the pinned v0.1.15 release; override with another build when the default enclave build lacks chart-required features like --mtu. | `string` | `"v0.1.15"` | no |
| enclaver\_image\_digest | Optional "sha256:..." digest pin for the server-mode enclaver sidecar. Empty (default) uses enclave\_image\_registry/enclave\_image\_tag; pin it to match the enclaver baked into the EIF (ENCLAVER\_DIGEST) so the TUN can't desync. | `string` | `""` | no |
| google\_admin\_emails | Setup admin emails for the bootstrap flow. | `list(string)` | `[]` | no |
| google\_oauth\_client\_id | Google OAuth client ID for the vault UI. | `string` | `""` | no |
| image\_tags | Per-component image tag overrides (orchestrator/policy\_server/mpc\_key\_store/ui). Pulled from sodot-docker-oci when set; the chart's pinned-release default applies when null. | <pre>object(\{<br />    mpc\_key\_store = optional(string, null)<br />    policy\_server = optional(string, null)<br />    orchestrator  = optional(string, null)<br />    ui            = optional(string, null)<br />  })</pre> | `{}` | no |
| internet\_facing | Master public/private switch for the hub's two front-door NLBs (vault UI + orchestrator API): true (this example's default) = internet-facing (public subnets); false = internal (VPC / VPN / PrivateLink only). Override per-LB with `ui_load_balancer.internet_facing` / `orchestrator_load_balancer.internet_facing` (each null = inherit). The cross-cluster NATS/relay/KS/POL NLBs are always internal. | `bool` | `true` | no |
| keystore\_image\_registry | Registry host+namespace for the MPC keystore enclave image. null (default) keeps the sodot-docker-oci production registry. The mpc\_key\_store\_image\_repository override wins when set. | `string` | `null` | no |
| keystore\_image\_tag | Tag for the MPC keystore enclave image (mirrors app\_image\_tag / enclave\_image\_tag). The base image tag also honors image\_tags.mpc\_key\_store. | `string` | `"v0.1.15"` | no |
| load\_balancer\_enabled | Master on/off switch for the hub's two front-door NLBs — the vault UI and the orchestrator REST API. true (this example's default) creates both; false creates neither. Override per-LB with `ui_load_balancer.enabled` / `orchestrator_load_balancer.enabled` (each null = inherit). The cross-cluster NATS/relay/KS/POL NLBs are unaffected. NOTE: because this defaults to true, you MUST set each enabled LB's `host` and a TLS certificate (`<lb>.certificate_arn`, or the matching `ui_cert_domain` / `orchestrator_cert_domain` for an ACM lookup) — `terraform plan` fails validation otherwise. | `bool` | `true` | no |
| mpc\_key\_store\_image\_repository | Per-image override for the MPC keystore enclave repository. null (default) derives \<keystore\_image\_registry>/mpc-key-store-aws-nitro; set to point it at a different repo. | `string` | `null` | no |
| observability | OTLP export for the non-enclave services (orchestrator + policy-server). Leave otlp\_endpoint empty to keep export off (stdout only). See the module's observability variable for the full field set and validations. | <pre>object(\{<br />    otlp\_endpoint            = optional(string, "")<br />    otlp\_protocol            = optional(string, "grpc") # "grpc" or "http/protobuf"<br />    otlp\_console             = optional(bool, true)     # also keep logging to stdout<br />    otlp\_timeout             = optional(string, "")<br />    otlp\_resource\_attributes = optional(string, "")<br />    otlp\_log\_filter          = optional(string, "")<br />    otlp\_headers\_secret\_name = optional(string, "")<br />    otlp\_headers\_secret\_key  = optional(string, "")<br />  })</pre> | `{}` | no |
| orchestrator\_cert\_domain | Domain of an ISSUED ACM certificate to look up for the orchestrator API NLB's TLS listener (e.g. "*.example.com"). The lookup runs in the HUB cluster's region/account. Ignored when orchestrator\_load\_balancer.certificate\_arn is set directly; required (one of the two) when the orchestrator API load balancer is enabled. | `string` | `""` | no |
| orchestrator\_load\_balancer | Expose the orchestrator REST API via an NLB — the SAME mechanism as the UI LB. `enabled` and `internet_facing` each override the matching master (`load_balancer_enabled` / `internet_facing`) for this LB; leave null to inherit. `host` (REQUIRED when enabled) is YOUR API hostname (e.g. vault-api.example.com), distinct from the UI host; SDK clients dial it. TLS: set `certificate_arn` to an ISSUED ACM cert in the hub region, or leave it empty and set `orchestrator_cert_domain` to look one up by domain. DNS: a Route53 alias for `host` is created in `ui_dns_zone` (the API host must live in the same zone); with it empty, point your own DNS at the `orchestrator_nlb_dns_name` output. | <pre>object(\{<br />    enabled         = optional(bool)<br />    internet\_facing = optional(bool)<br />    host            = optional(string, "")<br />    certificate\_arn = optional(string, "")<br />  })</pre> | `{}` | no |
| otlp\_exporter\_enabled | Run the enclave OTLP-exporter sidecar (OTEL logs -> stdout -> `kubectl logs`). Enabled by default — it is the enclave's only log path in production (the console is sealed). Set false only if you have an alternative. | `bool` | `true` | no |
| otlp\_exporter\_endpoint | Optional downstream OTLP collector for the enclave exporter's tee — a BASE URL (http://host:port, NO path; http:// only, the exporter has NO TLS, so use a plaintext in-cluster collector that handles any TLS egress to a vendor). Empty (default) = stdout only (`kubectl logs` still works). When set, the chart opens keystore egress to the endpoint's port. | `string` | `""` | no |
| otlp\_exporter\_format | Enclave OTLP-exporter stdout format for LOG records: "text" (a tracing-style line per record) or "json" (one JSON object per record, for a structured log pipeline). | `string` | `"text"` | no |
| otlp\_exporter\_protocol | Transport the enclave exporter's downstream tee speaks: "http" (default) or "grpc". Ignored when otlp\_exporter\_endpoint is empty. | `string` | `"http"` | no |
| prebuilt\_eif\_enabled | Mode B (pre-built measured EIF), the production default: the keystore runner consumes a CI-built, *measured* EIF (reproducible PCR0, pinnable in the KMS key policy) instead of building it in-container. Threaded into every cluster's nitro\_enclave via local.nitro\_enclave\_image\_tags. Requires prebuilt\_eif\_ref when enabled. Set false to fall back to Mode A (in-container build, non-deterministic PCR0) — DEV-ONLY: Mode A mounts the host docker.sock and cannot pin PCR0, so do not use it in production. | `bool` | `true` | no |
| prebuilt\_eif\_ref | Mode B: OCI ref of the pre-built measured EIF. Defaults to the published v0.1.15 unified EIF on the sodot-docker-oci production registry; override to pin a different reviewed release tag. Required non-empty whenever prebuilt\_eif\_enabled is true. | `string` | `"repo.sodot.dev/sodot-docker-oci/mpc-key-store-eif:v0.1.15"` | no |
| secrets\_recovery\_window\_days | Recovery window (in days) for the Secrets Manager secrets the clusters create. Set 0 to force-delete on destroy (no recovery window) so a re-deploy with the same deployment\_name does not collide with secrets pending deletion — handy for throwaway/CI runs. Default 30 (AWS default, production-safe). Passed through to all three clusters. | `number` | `30` | no |
| tags | Tags merged onto every taggable resource. | `map(string)` | <pre>\{<br />  "Project": "crypto-vault"<br />}</pre> | no |
| ui\_cert\_domain | Domain of an ISSUED ACM certificate to look up for the UI NLB's TLS listener (e.g. "*.example.com"). The lookup runs in the HUB cluster's region/account — the certificate must live there. Ignored when ui\_load\_balancer.certificate\_arn is set directly; required (one of the two) when the UI load balancer is enabled. | `string` | `""` | no |
| ui\_client\_secret\_ref | BYO mode: name of an existing Kubernetes Secret in the chart namespace containing the UI's Google OAuth client secret under key CLIENT\_SECRET. Forwarded to the hub module. Leave empty to use the literal-var path (`ui_google_oauth_client_secret`). | `string` | `""` | no |
| ui\_dns\_zone | Name of a public Route53 hosted zone (in the hub account) to create the UI + orchestrator-API host alias records in (e.g. "example.com"). Leave empty to skip DNS entirely — e.g. when your DNS lives outside Route53 or in another account — and point your own DNS at the ui\_nlb\_dns\_name / orchestrator\_nlb\_dns\_name outputs instead. | `string` | `""` | no |
| ui\_google\_oauth\_client\_secret | Convenience mode: literal Google OAuth client secret. Forwarded to the hub module, which creates a Kubernetes Secret and wires `ui.clientSecretRef` to it. Set in terraform.tfvars (gitignored) or via TF\_VAR\_ui\_google\_oauth\_client\_secret. | `string` | `""` | no |
| ui\_load\_balancer | Expose the hub's vault UI via an NLB. `enabled` and `internet_facing` each override the matching master (`load_balancer_enabled` / `internet_facing`) for this LB; leave null to inherit. `host` (REQUIRED when enabled) is YOUR UI hostname (e.g. vault.example.com): it drives the UI's REDIRECT\_URI + WebAuthn origin, must be covered by the TLS certificate, and https://\<host>/auth/callback must be registered on the Google OAuth client. TLS: set `certificate_arn` to an ISSUED ACM certificate in the hub region — or leave it empty and set `ui_cert_domain` to look one up by domain. DNS: a Route53 alias for `host` is created only when `ui_dns_zone` is set; with it empty, point your own DNS provider at the `ui_nlb_dns_name` output instead. | <pre>object(\{<br />    enabled         = optional(bool)<br />    internet\_facing = optional(bool)<br />    host            = optional(string, "")<br />    certificate\_arn = optional(string, "")<br />  })</pre> | `{}` | no |
| vpc\_single\_nat\_gateway | Route all private-subnet egress through one shared NAT gateway (cheap, but an egress SPOF) instead of one NAT per AZ. Defaults to false (HA). terraform.tfvars sets true to keep a PoC cheap (saves ~6 NAT gateways across 3 clusters). | `bool` | `false` | no |

### Outputs

| Name | Description |
| ---- | ----------- |
| helm\_handoff | Where the per-cluster Helm handoff files were written. Follow README.md "Installing the chart" for the Stage 1 / Stage 2 helm commands. |
| hub\_aws\_account\_id | Hub AWS account ID. |
| hub\_eks\_cluster\_name | Hub cluster name (use with `aws eks update-kubeconfig`). |
| kubectl\_hub | Command to update kubeconfig for the hub. |
| kubectl\_spoke\_0 | Command to update kubeconfig for the spoke. |
| kubectl\_spoke\_1 | Command to update kubeconfig for shard-2 spoke. |
| nats\_internal\_lb\_arn | ARN of the hub's NATS NLB (PrivateLink Endpoint Service backend). |
| nats\_internal\_lb\_hostname | DNS hostname of the hub's NATS NLB (handy for debugging — VPC peering case would dial this directly). |
| orchestrator\_nlb\_dns\_name | DNS name of the orchestrator API NLB. With ui\_dns\_zone empty, point your own DNS at this (CNAME orchestrator\_load\_balancer.host -> this name). null when the UI/API load balancer is disabled. |
| orchestrator\_nlb\_zone\_id | Canonical hosted-zone ID of the orchestrator API NLB — for an alias record in a Route53 zone this config doesn't manage. |
| orchestrator\_url | The orchestrator REST API URL (TLS terminates at the NLB). Reachable once DNS for the host points at the NLB — via the managed Route53 alias (ui\_dns\_zone set) or your own DNS (orchestrator\_nlb\_dns\_name). |
| privatelink\_endpoint\_id | Spoke-side PrivateLink Endpoint ID. |
| privatelink\_endpoint\_service\_name | AWS service name of the PrivateLink Endpoint Service on the hub side. |
| spoke\_0\_aws\_account\_id | Spoke AWS account ID. |
| spoke\_0\_eks\_cluster\_name | Spoke cluster name. |
| spoke\_1\_eks\_cluster\_name | Shard-2 spoke cluster name (cross-region). |
| spoke\_nats\_hub\_url | What the spoke cluster's chart reads as `nats.hub.url` — i.e. the PrivateLink-derived NATS endpoint hostname. |
| ui\_nlb\_dns\_name | DNS name of the UI NLB. With ui\_dns\_zone empty, point your own DNS at this (CNAME ui\_load\_balancer.host -> this name). null when the UI load balancer is disabled. |
| ui\_nlb\_zone\_id | Canonical hosted-zone ID of the UI NLB — for an alias record in a Route53 zone this config doesn't manage. |
| ui\_url | The vault UI URL (TLS terminates at the NLB). Reachable once DNS for the host points at the NLB — via the managed Route53 alias (ui\_dns\_zone set) or your own DNS (ui\_nlb\_dns\_name). |
