Install with Helm

Kubernetes is the supported production deployment path.

Before you start

PrerequisiteNotes
PostgreSQL 18External. A CloudNativePG example is in the chart: examples/cnpg-cluster.yaml
Valkey or RedisExternal. A minimal StatefulSet example is in the chart: examples/valkey-statefulset.yaml
Ingress controllerAny. The chart emits a standard Ingress; set ingress.className (default nginx)
cert-manager, or a TLS secretFor ingress.tls
StorageClassFor the dev_file key volume and local image storage, if used. ReadWriteMany when replicas span nodes
SMTP relayRequired

Download and verify the release first: Download and verify. The chart has no Bitnami dependency — those images moved to docker.io/bitnamilegacy in August 2025 and no longer receive security patches.

1. Pull access

The chart version is the release version without its leading v: release v0.34.28 is chart 0.34.28. Each chart pins that release's images by digest.

NS=lutraid
SLUG=<your-slug>
VERSION=0.34.28
kubectl create namespace $NS

helm registry login registry.lutralabs.io
kubectl -n $NS create secret docker-registry lutraid-registry \
  --docker-server=registry.lutralabs.io \
  --docker-username=<username> --docker-password=<password>

2. Secrets

Nothing sensitive goes in values. Every secret is referenced by name and mounted as a file, so values never appear in a pod spec and rotation is a restart rather than a manifest edit. External Secrets Operator, OpenBao and Vault integrate by creating these Secrets — the chart has no coupling to any of them.

kubectl -n $NS create secret generic lutraid-db   --from-literal=uri="postgresql://user:pass@host:5432/lutraid"
kubectl -n $NS create secret generic lutraid-auth --from-literal=secret="$(openssl rand -base64 48)"
# The first administrator's password — at least 8 characters. Delete it after install.
kubectl -n $NS create secret generic lutraid-admin --from-literal=password='<password>'

If your relay needs authentication, also create a Secret with its password and set email.smtp.username and email.smtp.existingSecret.

3. Install

helm install lutraid oci://registry.lutralabs.io/customers/$SLUG/lutralabs/charts/lutraid \
  --version $VERSION -n $NS \
  --set global.imageRegistry=registry.lutralabs.io/customers/$SLUG \
  --set 'global.imagePullSecrets[0].name=lutraid-registry' \
  --set host=id.example.gov.si \
  --set database.existingSecret=lutraid-db \
  --set auth.existingSecret=lutraid-auth \
  --set valkey.host=valkey.lutraid.svc.cluster.local \
  --set email.smtp.host=smtp.example.gov.si \
  --set email.from='LutraID <noreply@id.example.gov.si>' \
  --set bootstrap.superadmin.enabled=true \
  --set bootstrap.superadmin.email=you@example.gov.si \
  --set bootstrap.superadmin.existingSecret=lutraid-admin

host derives PUBLIC_BASE_URL and FRONTEND_URL. It becomes the issuer identifier embedded in every credential you issue — decide it before you go live.

Schema migrations run as a pre-install hook before any application pod starts. helm install fails fast with a message naming a missing value, rather than deploying a broken release.

4. First administrator and installation ID

kubectl -n $NS logs job/lutraid-bootstrap-superadmin
kubectl -n $NS delete secret lutraid-admin

The Job log ends with Installation ID: <uuid>. Keep it — a production license is bound to it. Sign in at https://<host>; the account is pre-verified.

5. Install the license

kubectl -n $NS create secret generic lutraid-license --from-file=license=license.lic
helm upgrade lutraid oci://registry.lutralabs.io/customers/$SLUG/lutralabs/charts/lutraid \
  --version $VERSION -n $NS --reuse-values --set license.existingSecret=lutraid-license

No license yet? LutraID runs normally for 30 days from first start. You can also install it through the API with no restart: see License.

Values worth knowing

values.yaml in the chart has the full set, and values.schema.json validates it at install time.

KeyPurpose
global.imageRegistryThe single registry value. The one thing to change when mirroring
global.imageMapFor registries that flatten paths — Harbor projects, ECR one-repo-per-image
ingress.className, ingress.tls.secretNameYour ingress controller and TLS secret
keys.backenddev_file (default) or aws_kms. See Signing keys
imageStorage.driverlocal (a PVC) or s3. S3 works against MinIO and Ceph through an endpoint override
email.transportsmtp (default) or resend
<workload>.replicaCount, .resources, .nodeSelector, .affinity, .tolerationsPer workload: backend, frontend, worker, outboxRelay, statusPublisher
maintenance.tasks.*Five retention CronJobs. Do not disable them — they are how expired data is actually deleted

Verify the install

helm test lutraid -n $NS
kubectl -n $NS get pods -l app.kubernetes.io/instance=lutraid

helm test runs a health probe. If the install failed, the migration Job is deliberately left in place so its logs survive.

Was this page helpful?