> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nora.my/llms.txt
> Use this file to discover all available pages before exploring further.

# Kubernetes (Helm)

> Deploy Nora on a Kubernetes cluster with the nora-onprem Helm chart. Ingress, TLS, private registry, and license overrides.

The `nora-onprem` Helm chart deploys the same five services as [Docker Compose](/local-app/deploy/compose) onto a Kubernetes cluster. The chart lives at `infra/onprem/helm/nora-onprem` in the platform repo.

## Install

`postgres.password` is the only required value on a fresh install.

```bash theme={null}
helm install nora ./nora-onprem \
  --namespace nora --create-namespace \
  --set postgres.password='CHANGE_ME' \
  --set publicUrl='http://nora.example.internal'
```

The chart provisions Deployments, StatefulSets, and Services under the release name. See [What the chart creates](#what-the-chart-creates) for the exact resource list.

## Common overrides

### Enable ingress and TLS

```bash theme={null}
--set ingress.enabled=true \
--set ingress.host=nora.example.internal \
--set ingress.className=nginx \
--set ingress.tlsSecret=nora-tls
```

Setting `ingress.tlsSecret` auto-enables `NORA_COOKIE_SECURE=1` — browsers require the `Secure` attribute on session cookies over TLS.

### Point at a private registry (air-gapped)

Load the tarball images into your own registry (see [Air-gap tarball](/local-app/deploy/airgap)), then override each repository:

```bash theme={null}
--set image.server.repository=registry.internal/nora-server \
--set image.server.tag=1.0.0 \
--set image.llmBridge.repository=registry.internal/nora-llm-bridge \
--set image.llmBridge.tag=1.0.0 \
--set imagePullSecrets[0].name=my-registry-secret
```

### Turn on the air-gap boot probe

```bash theme={null}
--set deploy.airgap=true
```

The server exits with code 78 at boot if the probe target (default `1.1.1.1:443`) is still reachable — a smoke test for the operator's egress isolation.

### Attach an offline license

```bash theme={null}
kubectl -n nora create secret generic nora-license \
  --from-file=license.json=./license.json

helm upgrade nora ./nora-onprem -n nora \
  --set license.fileSecret=nora-license --reuse-values
```

The chart mounts the secret at `/etc/nora/license.json` and sets `NORA_LICENSE_FILE` for the server.

### Switch to Qdrant for retrieval

```bash theme={null}
--set vectorStore.backend=qdrant
```

The default is `pgvector` (bundled with the Postgres pod). Flip to `qdrant` when you want vector search on a dedicated pod.

## What the chart creates

| Resource | Type | Purpose |
| - | - | - |
| `<release>-nora-onprem-server` | Deployment + Service | Rust API and SPA (port 8090) |
| `<release>-nora-onprem-llm-bridge` | Deployment + Service | LLM subprocess bridge (port 8091) |
| `<release>-nora-onprem-postgres` | StatefulSet + Service | Postgres with pgvector (one PVC per replica) |
| `<release>-nora-onprem-redis` | Deployment + Service | Cache |
| `<release>-nora-onprem-qdrant` | StatefulSet + Service | Vector store (optional) |
| `<release>-nora-onprem-secrets` | Secret | DB password and license key |
| `<release>-nora-onprem` | Ingress | Only when `ingress.enabled=true` |

## Not in scope yet

* **Multi-replica server.** LLM subprocess HOME isolation hasn't landed, so server pods run at `concurrency=1`.
* **HPA and autoscaling.** Waits on the above.
* **Backup and restore jobs.** Use standard Postgres tooling against the StatefulSet's PVC.
* **Cert-manager Issuer wiring.** Bring your own Issuer; the chart just consumes a `Secret` you point `ingress.tlsSecret` at.

## Upgrade

```bash theme={null}
helm upgrade nora ./nora-onprem -n nora \
  --set image.server.tag=1.2.0 \
  --set image.llmBridge.tag=1.2.0 \
  --reuse-values
```

Migrations run automatically on the first new server pod (`NORA_RUN_MIGRATIONS=1`).

<Warning>
  Migrations are additive-only. Pin a matching database snapshot before downgrading across a schema advance.
</Warning>

## Uninstall

```bash theme={null}
helm uninstall nora -n nora
kubectl delete namespace nora   # drops the PVCs, too
```

`helm uninstall` alone keeps the PVCs, so your data survives an accidental delete. Drop the namespace when you're sure.

## Related

<CardGroup cols={2}>
  <Card title="Docker Compose" icon="docker" href="/local-app/deploy/compose">
    The same five services on a single host.
  </Card>

  <Card title="Air-gap tarball" icon="box-archive" href="/local-app/deploy/airgap">
    Building the images you'd push into your private registry.
  </Card>
</CardGroup>
