Skip to main content

Deploy ThunderID on Kubernetes

This guide walks you through deploying ThunderID to a Kubernetes cluster using Helm charts. It covers a quick single-command install for development and a production-ready setup with external PostgreSQL.

Architecture Overview

The diagram above shows the ThunderID deployment in Kubernetes, including the application pods, ingress controller, and database configuration.

Prerequisites

Before you begin, ensure the following are available:

Infrastructure:

  • A running Kubernetes cluster (v1.25 or later). You can use minikube or kind locally, or a managed service such as EKS, GKE, or AKS for production.
  • An NGINX Ingress Controller or a compatible alternative.
  • Valid TLS certificates for production deployments.

Required Tools:

ToolInstallation GuideVersion Check
GitInstall Gitgit --version
HelmInstall Helmhelm version
kubectlInstall kubectlkubectl version
DockerInstall Dockerdocker --version

Verify cluster access before proceeding:

kubectl cluster-info
helm version
kubectl get pods -n ingress-nginx

Install ThunderID

Step 1: Install the Helm Chart

Install ThunderID from the GitHub Container Registry:

helm install thunderid oci://ghcr.io/thunder-id/helm-charts/thunderid \
--namespace thunderid \
--create-namespace

To install a specific version:

helm install thunderid oci://ghcr.io/thunder-id/helm-charts/thunderid \
--version latest \
--namespace thunderid \
--create-namespace

Step 2: Verify the Installation

# Check pod status
kubectl get pods -l app.kubernetes.io/name=thunderid -n thunderid

# Check services
kubectl get services -l app.kubernetes.io/name=thunderid -n thunderid

# Check ingress
kubectl get ingress -n thunderid

Step 3: Access ThunderID

  1. Get the external IP address of your NGINX Ingress Controller.
  2. Add an entry to your /etc/hosts file that maps the IP address to thunderid.local.
  3. Open ThunderID at http://thunderid.local.

If you are using a cloud provider, the load balancer assigns the external IP automatically.

Installation Options

Option 1: Inline Value Overrides

Pass configuration values directly on the command line. The following example installs ThunderID with SQLite:

helm install thunderid oci://ghcr.io/thunder-id/helm-charts/thunderid \
--namespace thunderid \
--create-namespace \
--set configuration.database.config.type=sqlite \
--set configuration.database.runtime_transient.type=sqlite \
--set configuration.database.entity.type=sqlite \
--set configuration.database.runtime_persistent.type=sqlite \
--set deployment.securityContext.readOnlyRootFilesystem=false
warning

SQLite on Kubernetes stores database files inside the pod. Without a PersistentVolumeClaim, all data is lost when the pod restarts or is rescheduled to a different node. SQLite also does not support concurrent writes, so replicaCount must remain 1. See SQLite for PVC configuration. For multi-replica or durable deployments, use PostgreSQL.

note

--set values are not saved anywhere. Running helm get values thunderid will only show them if you pass --all. For anything beyond a quick throwaway install, prefer a values file: it is auditable, version-controllable, and reusable across upgrades. See Option 2 for the values-file approach.

Option 2: Custom Values File

For production deployments, use a values file to manage configuration:

  1. Create a custom-values.yaml file:

    deployment:
    replicaCount: 3
    resources:
    requests:
    cpu: 500m
    memory: 512Mi
    limits:
    cpu: 2
    memory: 1Gi

    ingress:
    hostname: thunderid.example.com

    configuration:
    database:
    config:
    type: postgres
    host: postgres.default.svc.cluster.local
    port: 5432
    name: configdb
    username: thunderid_user
    password: <config-db-password>
    sslmode: require
    runtime_transient:
    type: postgres
    host: postgres.default.svc.cluster.local
    port: 5432
    name: runtime_transient
    username: thunderid_user
    password: <runtime-transient-db-password>
    sslmode: require
    entity:
    type: postgres
    host: postgres.default.svc.cluster.local
    port: 5432
    name: entitydb
    username: thunderid_user
    password: <entity-db-password>
    sslmode: require
    runtime_persistent:
    type: postgres
    host: postgres.default.svc.cluster.local
    port: 5432
    name: runtime_persistent
    username: thunderid_user
    password: <runtime-persistent-db-password>
    sslmode: require
  2. Install using the values file:

    helm install thunderid oci://ghcr.io/thunder-id/helm-charts/thunderid \
    --namespace thunderid \
    --create-namespace \
    -f custom-values.yaml
warning

Never write database passwords directly into custom-values.yaml or commit it to version control. Instead, store credentials in a Kubernetes Secret and reference them using valueFrom.secretKeyRef, or use a tool such as External Secrets Operator or Sealed Secrets to manage secrets outside the values file.

Database Setup

ThunderID supports both PostgreSQL and SQLite. PostgreSQL is recommended for production.

PostgreSQL

Before deploying ThunderID, prepare the PostgreSQL instance:

  1. Create the four required databases:

    CREATE DATABASE configdb;
    CREATE DATABASE runtime_transient;
    CREATE DATABASE entitydb;
    CREATE DATABASE runtime_persistent;
  2. Create a dedicated user:

    CREATE USER thunderid_user WITH PASSWORD '<secure-password>';
  3. Grant the required privileges in each database. Connect to each database and run both statements. Using psql:

    for db in configdb runtime_transient entitydb runtime_persistent; do
    psql -h <db-host> -U postgres -d "$db" <<'SQL'
    GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO thunderid_user;
    ALTER DEFAULT PRIVILEGES IN SCHEMA public
    GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO thunderid_user;
    SQL
    done

    GRANT ON ALL TABLES covers tables that already exist. ALTER DEFAULT PRIVILEGES ensures tables created by future migrations are also accessible.

  4. Run the initialization scripts to create the schema:

For a PostgreSQL setup using Helm, refer to the Bitnami PostgreSQL Helm Chart.

Once the databases are ready, configure ThunderID to connect to them:

configuration:
database:
config:
type: postgres
host: postgres.example.com
port: 5432
name: configdb
username: thunderid_user
password: <config-db-password>
sslmode: require
runtime_transient:
type: postgres
host: postgres.example.com
port: 5432
name: runtime_transient
username: thunderid_user
password: <runtime-transient-db-password>
sslmode: require
entity:
type: postgres
host: postgres.example.com
port: 5432
name: entitydb
username: thunderid_user
password: <entity-db-password>
sslmode: require
runtime_persistent:
type: postgres
host: postgres.example.com
port: 5432
name: runtime_persistent
username: thunderid_user
password: <runtime-persistent-db-password>
sslmode: require

SQLite

For single-node setups, configure ThunderID to use SQLite:

configuration:
database:
config:
type: sqlite
sqlitePath: database/configdb.db
sqliteOptions: "_journal_mode=WAL&_busy_timeout=5000&_pragma=foreign_keys(1)"
runtime_transient:
type: sqlite
sqlitePath: database/runtime_transient.db
sqliteOptions: "_journal_mode=WAL&_busy_timeout=5000&_pragma=foreign_keys(1)"
entity:
type: sqlite
sqlitePath: database/entitydb.db
sqliteOptions: "_journal_mode=WAL&_busy_timeout=5000&_pragma=foreign_keys(1)"
runtime_persistent:
type: sqlite
sqlitePath: database/runtime_persistent.db
sqliteOptions: "_journal_mode=WAL&_busy_timeout=5000&_pragma=foreign_keys(1)"

SQLite database files are stored inside the pod by default. To persist data across restarts and rescheduling, enable a PersistentVolumeClaim in the Helm values:

persistence:
enabled: true
storageClass: "standard" # replace with your cluster's StorageClass
accessMode: ReadWriteOnce
size: 1Gi

Also set deployment.securityContext.readOnlyRootFilesystem: false when using SQLite. The chart defaults it to true, which blocks the setup job and server from writing to the container filesystem, including the temporary files created during initialization.

Set replicaCount: 1 when using SQLite: multiple replicas writing to the same SQLite files will corrupt the database.

Cryptographic Material

When setup.enabled is true (the default), the setup job generates per-install TLS, JWT signing, and AES encryption keys into a dedicated certs PersistentVolumeClaim, and the server reads them from it. This PVC must persist for the life of the deployment: if it is lost, previously encrypted data cannot be decrypted and previously issued tokens can no longer be validated. Configure its size and storage class under certs.persistence.

The certs PVC defaults to ReadWriteOnce, which fits a single-node or single-replica install. If you run more than one replica and the pods can land on different nodes, set certs.persistence.accessMode to ReadWriteMany with a storage class that supports it, so every replica can read the material. Alternatively, provide your own key material and set setup.enabled to false.

For production TLS, terminate HTTPS at your ingress or gateway with a CA-issued certificate (ingress.tlsSecretsName / gateway.tls); the certificate the setup job generates for the server is self-signed.

Health Checks

The chart configures startup, readiness, and health probes automatically. Override the timing values under deployment: in your custom-values.yaml if you need to adjust them:

deployment:
startupProbe:
initialDelaySeconds: 1
periodSeconds: 2
failureThreshold: 30
livenessProbe:
periodSeconds: 10
readinessProbe:
initialDelaySeconds: 1
periodSeconds: 10

The probe endpoints and ports are defined by the chart and do not need to be specified.

Automatic Scaling

To scale ThunderID automatically based on CPU and memory use, enable the HPA in your values file:

deployment:
replicaCount: 2 # sets the minimum replica count

hpa:
enabled: true
maxReplicas: 10
averageUtilizationCPU: 65
averageUtilizationMemory: 75

HPA requires the Kubernetes Metrics Server to be installed in your cluster. HPA is only compatible with PostgreSQL. Do not enable it when using SQLite.

Image Pull Secrets

If your cluster has GitHub Container Registry rate limits, or if you are mirroring the image to a private registry, configure imagePullSecrets.

Create a registry credential secret in the thunderid namespace:

kubectl create secret docker-registry ghcr-credentials \
--docker-server=ghcr.io \
--docker-username=<github-username> \
--docker-password=<github-personal-access-token> \
--namespace thunderid

Reference it in your values file:

imagePullSecrets:
- name: ghcr-credentials

Next Steps

Explore with AI

ThunderID LogoThunderID Logo

Product

DocsAPIsSDKs
© Copyright Linux Foundation Europe.For web site terms of use, trademark policy and other project policies please see https://linuxfoundation.eu/en/policies.