Skip to content

Kubernetes

zach115th edited this page Aug 5, 2026 · 10 revisions

Kubernetes

IRIS-NG ships a Helm chart at deploy/kubernetes/charts (chart 0.5.1, appVersion IRIS-NG-v1.1.1).

Status. The chart tracks the current stack: published images, an AI worker deployment, and a normal cluster volume instead of a node-local one. Verified on a live single-node cluster (kind, Kubernetes 1.34) — all five pods reach Running, the schema is created, and the app serves HTTP 200. It has not been tested on a managed multi-node cluster, where node rescheduling and a real CSI driver are the parts that differ. See Test results. Docker Compose on a single host remains the configuration this project tests continuously.

Try it on DigitalOcean

DigitalOcean Referral Badge

DigitalOcean Kubernetes (DOKS) is a straightforward place to run the chart, and the badge above is a referral link — it gives you free starting credit, and credits this project. Nothing in IRIS-NG depends on DigitalOcean; the chart is plain Helm and runs on any conformant cluster.

Images

Public images are on GHCR, multi-arch (linux/amd64, linux/arm64), pullable anonymously — no registry secret needed.

The chart does not pin an image tag. values.yaml ships tag: "" for the iris-ng images and the templates fall back to the chart's own appVersion, so a packaged chart always deploys the release it shipped with. Override per component if you need to (irisapp.tag, irisworker.tag, irisaiworker.tag, postgres.tag); rabbitmq is an upstream image on its own version and is unaffected.

Charts attached to releases before IRIS-NG-v1.0.1 did pin, and the one attached to IRIS-NG-v1.0.0 pinned the previous release's images — installing from that asset deploys v2.5.0-beta.1+iris-ng.7 despite the chart being labelled v1.0.0. Use IRIS-NG-v1.0.1 or later.

docker pull ghcr.io/zach115th/iriswebapp_app:IRIS-NG-v1.1.1
docker pull ghcr.io/zach115th/iriswebapp_db:IRIS-NG-v1.1.1

The image tag matches the release name under the current IRIS-NG-v<x.y.z> scheme. Releases made under the older +iris-ng.<build> scheme substitute - for + in their image tags, since + is not valid in a Docker tag.

The app image runs three roles; the entrypoint target selects which. There is no nginx deployment — the ingress controller terminates TLS in its place.

Installing

git clone https://github.com/zach115th/iris-ng.git && cd iris-ng

# Ingress controller (once per cluster)
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm install ingress ingress-nginx/ingress-nginx -n iris --create-namespace

cp deploy/kubernetes/charts/values.yaml my-values.yaml
$EDITOR my-values.yaml          # see "What you must change" below

helm install iris-ng ./deploy/kubernetes/charts -n iris -f my-values.yaml

Point DNS at the ingress load balancer once it has an external IP:

kubectl get svc -n iris ingress-ingress-nginx-controller

What you must change

The chart installs with defaults, but three things are placeholders you are expected to replace.

The ingress hostname. ingress.hosts[0].host and ingress.tls[0].hosts[0] both default to iris.example.com.

Every secret. IRIS_SECRET_KEY, IRIS_SECURITY_PASSWORD_SALT, the Postgres passwords and IRIS_ADM_PASSWORD all ship with well-known development values, and they appear in values.yaml in plaintext. Change them, and prefer wiring them to a Kubernetes Secret rather than committing a filled-in values file.

Storage size. postgres.persistence.size defaults to 20 GiB.

Persistence — read this before storing real cases

Case data, evidence metadata and AI artifacts all live in Postgres, so the database volume is the thing that matters.

By default the chart now requests a normal ReadWriteOnce volume from the cluster default StorageClass (do-block-storage on DOKS). Earlier chart versions hardcoded a hostPath PersistentVolume with ReadWriteMany, which is wrong twice over on a managed cluster: hostPath is node-local, so the database comes up silently empty if the pod ever reschedules onto another node, and ReadWriteMany is unsupported by most block-storage CSI drivers.

postgres:
  persistence:
    enabled: true
    storageClass: ""          # "" = cluster default
    accessMode: ReadWriteOnce
    size: 20Gi
    hostPath:
      enabled: false          # true only for kind/minikube single-node clusters

Confirm the claim actually bound before putting anything real on it:

kubectl get pvc -n iris

Back up that volume. scripts/import_vanilla_db.sh covers migrating an existing database in.

The AI worker

irisaiworker is deployed by default and consumes the dedicated ai_queue at concurrency 1 — one model call in flight at a time.

Keep replicaCount at 1. The bound is deliberate; scaling it up multiplies concurrent load on whatever backend is serving the model.

If you are not using the AI layer you can set irisaiworker.enabled: false. Do not disable it while an AI backend is configured — the executive case summary and case chat would enqueue jobs that nothing consumes, and the UI would show them queued forever rather than reporting an error. See AI Features → Async job queue.

The model itself runs outside the cluster in most setups; if that is a local LM Studio or Ollama, it must be reachable from the pods.

TLS material

The app and worker mount four Secrets built from files under charts/certificates/ via .Files.Glob. That directory is not shipped with the chart, so this is off by default (certificates.enabled: false) — otherwise the Secrets render empty and mount an empty directory where the CA file should be.

Enable it only after placing rootCA/irisRootCACert.pem, rootCA/*, web_certificates/* and ldap/* under charts/certificates/. This is for the application's own trust store and LDAP; for ingress TLS use cert-manager with a ClusterIssuer plus ingress.tls.

Database authentication

postgres.POSTGRES_HOST_AUTH_METHOD now defaults to scram-sha-256. Earlier chart versions hardcoded trust, which accepts any connection reaching the service with no password at all. Set it back to trust only if you deliberately want an unauthenticated database.

Sizing

Component Requests
iris_app 1 CPU / 2 GB
iris_worker 0.5 CPU / 1 GB
ai_worker 0.5 CPU / 1 GB
postgres 1 CPU / 2 GB + 20 GB volume
rabbitmq 0.25 CPU / 512 MB

Two 4 GB nodes is a reasonable starting point.

Simpler: Docker Compose on one host

For evaluation, a single 4 GB Droplet running the compose stack is faster to stand up and is the configuration this project actually tests:

git clone https://github.com/zach115th/iris-ng.git && cd iris-ng
bash scripts/generate_dev_certs.sh
bash scripts/iris_helper.sh --init
docker compose -f docker-compose.dev.yml up -d --build

Full walkthrough in Getting Started.

Test results

Verified on kind, Kubernetes 1.34, single node, installing with the shipped defaults and no overrides:

Check Result
helm install with defaults succeeds, no overrides needed
Pods 5/5 Running — app, worker, ai-worker, postgres, rabbitmq
PVC Bound, 20Gi, RWO, cluster default StorageClass
Schema 95 tables, Alembic at head, migrations ran
App HTTP 200 on /login, locally and cross-pod via Service DNS
AI worker bound to ai_queue, concurrency: 1, reports ready
Postgres auth pg_hba shows scram-sha-256 and the app connects
irisaiworker.enabled=false AI worker cleanly removed, rest unaffected

That last Postgres row is the meaningful one: password auth is enforced and the application authenticates, which could not happen while the env var was misspelled.

Not covered: any managed multi-node cluster. Node rescheduling, a real CSI driver, ingress-nginx with a cloud load balancer and cert-manager are exactly the parts a single-node test cannot exercise.

Known gaps

  • Only single-node tested — see Test results.
  • Secrets are plaintext values, not Kubernetes Secrets.
  • No HorizontalPodAutoscaler, PodDisruptionBudget, NetworkPolicy or resource defaultsresources: {} means pods are BestEffort until you set them.
  • Postgres is a Deployment, not a StatefulSet, with replicaCount exposed; running more than one replica against one RWO volume will not work.

Reports from a real install are welcome — see CONTRIBUTING.md.

Clone this wiki locally