Deploy ThunderID as an OpenChoreo Resource
This guide walks you through running ThunderID as an OpenChoreo Resource. A platform administrator registers the ResourceType once, and teams then create a Resource from it to provision a ThunderID instance.
Key characteristics of this deployment model:
- Provisioning is fully declarative. Every ThunderID resource, organization units, user types, applications, flows, themes, and users, comes from a single resources file supplied at
Resourcecreation time. - Sensitive values are materialized from the platform secret store by External Secrets Operator.
- The database backend is a parameter. The default
sqlitemode needs no external database;postgresmode targets an externally hosted PostgreSQL instance.
Prerequisites
Before you begin, ensure the following are available:
Infrastructure:
- A running Kubernetes cluster with OpenChoreo installed (control plane and data plane).
- External Secrets Operator with a
ClusterSecretStore(or namespacedSecretStore) configured for the data plane. The OpenChoreo quick-start installs one backed by OpenBao. - For
postgresmode only: an accessible PostgreSQL instance, in-cluster or external.
Required Tools:
| Tool | Installation Guide | Version Check |
|---|---|---|
| Git | Install Git | git --version |
| Helm | Install Helm | helm version |
| kubectl | Install kubectl | kubectl version |
| OpenChoreo | Install OpenChoreo | kubectl get crd | grep openchoreo |
Verify cluster access and the required custom resource definitions before proceeding:
kubectl cluster-info
kubectl get crd | grep -E 'resourcetypes|resources|resourcereleases|resourcereleasebindings'
kubectl get clustersecretstore
How It Works
- The
thunderid-oc-resourcetypechart registers thethunderidClusterResourceType, which holds the schema and the Kubernetes templates. - You create a
Resourcethat references theClusterResourceTypeand supplies the instance parameters. OpenChoreo cuts aResourceReleasefrom it. - A
ResourceReleaseBindingpins theResourceReleaseto an environment. - OpenChoreo renders the workload into the data-plane namespace: an
ExternalSecretmaterialized from the platform secret store, the configurationConfigMapresources, theDeployment, theService, and theHTTPRoute.
Install the ResourceType
A platform administrator installs the chart once per cluster:
helm install thunderid-type install/openchoreo/thunderid-oc-resourcetype
This registers the thunderid ClusterResourceType. To register a namespaced ResourceType instead, set resourceType.cluster=false.
Generate the Key Material
ThunderID needs an AES encryption key and two token-signing key pairs, one RSA and one ECDSA. The container image ships no key material, so generate it before the first deployment. Self-signed signing pairs are fine here; these keys sign tokens, they do not terminate TLS. The signing certificates must carry a subject alternative name, which the OpenID4VP verifier requires at startup.
# AES encryption key (64 hex characters)
openssl rand -hex 32 > crypto.key
# RSA signing pair
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout signing.key -out signing.cert \
-subj "/CN=localhost" -addext "subjectAltName=DNS:localhost,IP:127.0.0.1"
# ECDSA signing pair
openssl ecparam -name prime256v1 -genkey -noout -out ecdsa-signing.key
openssl req -new -x509 -nodes -days 3650 -key ecdsa-signing.key \
-out ecdsa-signing.cert \
-subj "/CN=localhost" -addext "subjectAltName=DNS:localhost,IP:127.0.0.1"
Store the Environment Values
Push the sensitive values, together with the encryption key and signing key files, to the platform secret store as properties of one secret per environment. Scalar values such as ADMIN_PASSWORD become container environment variables; the crypto.key, certificate, and signing-key properties are mounted as files under config/certs/. The following example uses the OpenBao instance installed with the OpenChoreo quick-start:
bao kv put secret/thunderid/dev \
ADMIN_PASSWORD=<admin-password> \
crypto.key="$(cat crypto.key)" \
signing.cert="$(cat signing.cert)" signing.key="$(cat signing.key)" \
ecdsa-signing.cert="$(cat ecdsa-signing.cert)" ecdsa-signing.key="$(cat ecdsa-signing.key)"
bao kv put replaces the entire secret, so include every property in a single command.
The quick-start OpenBao is not exposed outside the cluster, so run the bao command inside its pod. In development mode the root token is root:
kubectl exec -n openbao openbao-0 -- env BAO_TOKEN=root \
bao kv put secret/thunderid/dev \
ADMIN_PASSWORD=<admin-password> \
crypto.key="$(cat crypto.key)" \
signing.cert="$(cat signing.cert)" signing.key="$(cat signing.key)" \
ecdsa-signing.cert="$(cat ecdsa-signing.cert)" ecdsa-signing.key="$(cat ecdsa-signing.key)"
The $(cat ...) expansions run in your local shell, so the key files are read locally and their contents passed to bao inside the pod.
For postgres mode, add the database connection properties for each of the four databases:
bao kv put secret/thunderid/dev \
ADMIN_PASSWORD=<admin-password> \
crypto.key="$(cat crypto.key)" \
signing.cert="$(cat signing.cert)" signing.key="$(cat signing.key)" \
ecdsa-signing.cert="$(cat ecdsa-signing.cert)" ecdsa-signing.key="$(cat ecdsa-signing.key)" \
DB_CONFIG_HOSTNAME=<db-host> DB_CONFIG_PORT=5432 DB_CONFIG_NAME=configdb \
DB_CONFIG_USERNAME=<db-user> DB_CONFIG_PASSWORD=<db-password> DB_CONFIG_SSLMODE=require \
# ... repeat the six properties for DB_RUNTIME_TRANSIENT_*, DB_ENTITY_*, and DB_RUNTIME_PERSISTENT_*
The ResourceType renders an ExternalSecret that extracts every property at the store path. Scalar values are injected into the container environment, where ThunderID resolves the {{.VAR}} placeholders in its configuration at startup; the crypto.key, certificate, and signing-key properties are mounted as files under config/certs/.
Populate every value before the first deployment. Because the rendered release name is stable, an ExternalSecret created on the first deployment does not pick up later store changes until its refresh interval elapses.
Create the Resource
Use the sample manifests at install/openchoreo/thunderid-oc-resourcetype/samples/resource.yaml as a starting point. Replace the <SERVER_PUBLIC_URL> and <GATEWAY_HOSTNAME> placeholders, then apply:
kubectl apply -f install/openchoreo/thunderid-oc-resourcetype/samples/resource.yaml
A minimal Resource references the secret store and provides the declarative resources file:
apiVersion: openchoreo.dev/v1alpha1
kind: Resource
metadata:
name: thunderid
namespace: default
spec:
owner:
projectName: default
type:
kind: ClusterResourceType
name: thunderid
parameters:
secretStore:
name: default
key: thunderid/dev
declarativeResources: |
# Multi-document resources file — organization units, user types,
# applications, flows, themes, and users. Export one from an existing
# instance through the /export API and paste it here.
Applying the Resource cuts a ResourceRelease automatically.
The sample derives both the System resource server identifier and the Console's resource indicator from the server public URL, as <SERVER_PUBLIC_URL>/mcp. Substituting <SERVER_PUBLIC_URL> keeps the two aligned, so the Console's token requests match the registered resource server. Keep them equal if you customize either.
Exports strip user credentials. Add a credentials: block to each user that must sign in, and resolve the password from the secret store so it never appears in the manifest:
credentials:
password: "{{.ADMIN_PASSWORD}}"
Deploy to an Environment
Applying the sample manifest in the previous step also created a ResourceReleaseBinding named thunderid-development. This binding deploys the release to the development environment, and stays pending until you pin a release to it.
List the releases cut from the Resource, then pin the one you want to the binding:
kubectl get resourcerelease -n default
kubectl patch resourcereleasebinding thunderid-development -n default \
--type=merge -p '{"spec":{"resourceRelease":"<release-name>"}}'
Use the release name from the kubectl get resourcerelease output as <release-name>.
Verify the Deployment
# Binding status
kubectl get resourcereleasebinding thunderid-development -n default \
-o jsonpath='{range .status.conditions[*]}{.type}={.status}{"\n"}{end}'
# Rendered workload in the data-plane namespace
kubectl get all,externalsecret -n <data-plane-namespace>
The ThunderID server is reachable at the external gateway hostname the HTTPRoute generates, in the form <environmentName>-<resourceName>-<resourceNamespace>.<gateway-host>. The <gateway-host> comes from the data plane's gateway.ingress.external.http (or .https) host.
Database Modes
Set the database engine through the runtime.dbType parameter.
SQLite (Default)
sqlite mode uses the database files bundled in the container image. It needs no external database and no DB_* properties in the secret store.
SQLite storage is pod-local and ephemeral. Runtime data, sessions, tokens, and database-backed stores, is lost on pod restart, and replicas must stay at 1. Use SQLite for development and evaluation only.
PostgreSQL
postgres mode targets an externally hosted PostgreSQL instance. Set runtime.dbType: postgres, and provide the DB_* properties in the secret store.
Prepare the PostgreSQL instance before deployment:
-
Create the four required databases:
CREATE DATABASE configdb;
CREATE DATABASE runtime_transient;
CREATE DATABASE entitydb;
CREATE DATABASE runtime_persistent; -
Load the schema into each database. The scripts ship in the ThunderID repository under
backend/dbscripts; clone it if you have not already. Run each script against its database:psql -h <db-host> -U <db-user> -d configdb -f backend/dbscripts/configdb/postgres.sql
psql -h <db-host> -U <db-user> -d runtime_transient -f backend/dbscripts/runtime_transient/postgres.sql
psql -h <db-host> -U <db-user> -d entitydb -f backend/dbscripts/entitydb/postgres.sql
psql -h <db-host> -U <db-user> -d runtime_persistent -f backend/dbscripts/runtime_persistent/postgres.sql
Load the schema scripts from the same version as the container image set in parameters.image. A mismatch between the schema and the running server causes startup or runtime failures.
Certificates, TLS, and Configuration Overrides
The ResourceType exposes further parameters for production hardening:
runtime.tls.enabledserves HTTPS from the container and configures the gateway to originate TLS to the backend. It requires the serving pair (server.cert/server.key) as additional properties at the store path.runtime.certs.storeKeyreads the certificate and key files from a separate store entry instead ofsecretStore.key, for independent rotation or access control.configOverridesreplaces the generateddeployment.yaml, Gateconfig.js, or Consoleconfig.jsfiles when the built-in templates do not cover a setting.
For the full parameter reference, see the chart documentation at install/openchoreo/thunderid-oc-resourcetype/README.md.
Environment Management
OpenChoreo provides built-in environment management with promotion workflows. Three environments are available out of the box:
- Development: for active development and testing.
- Staging: for pre-production validation.
- Production: for live traffic.
To promote ThunderID to another environment, create a ResourceReleaseBinding for that environment, provide its secret store values, and pin the same release.