Product documentation
Quickstart: self-managed (Kubernetes + Helm)
Deploy the Thoryn platform to your own Kubernetes cluster with the Helm chart and verify every service comes up Ready — the happy path to a running stack.
Quickstart: self-managed (Kubernetes + Helm)
This tutorial deploys the Thoryn platform to a Kubernetes cluster you control and verifies it comes up healthy. Self-managed runs the identical artefacts as the managed service — the same images, the same Helm chart, the same Flyway migrations and configuration schema (see Delivery-model architecture). First success here is a running stack with a reachable OIDC discovery document; configuring tenants and applications afterward is the same self-service workflow the managed service uses.
Scope of this page. This is the happy-path deploy-and-verify. It is not the full production operations runbook. The complete procedure — per-environment secret provisioning, DNS and TLS (cert-manager), the environment values overlay, upgrades, and backup / restore — will live in the Operations docs under
docs/content/operate/, which are in progress. Until they land, this page points you at the Configuration reference and the chart's own reference values files for depth.
Prerequisites
- A Kubernetes cluster you can deploy to, and
kubectlconfigured for it. - Helm v3.
- The Thoryn Helm chart, at
deploy/helm/thorynin this repository (chartthoryn). - A container registry your cluster can pull the Thoryn images from, and the immutable image tag (a git SHA) you intend to deploy.
- A DNS domain you control for the platform hosts (the hub issuer, identity, and the API gateway), with TLS termination available to your ingress.
What the chart deploys
One helm upgrade --install brings up the whole platform in your namespace:
| Component | Role |
|---|---|
| authorization-hub | The OAuth 2.0 / OIDC broker — issues tokens; owns no user data. |
| product-api | The customer-plane management API (tenants, applications, federation, audit). |
| api-gateway | Public ingress for product-api (TLS, edge rate limiting, bearer validation). |
| identity-service | Thoryn's own OIDC provider — users, login UI, SCIM — as a federation member. |
| Postgres (StatefulSet) | Platform database; Flyway migrations run on service startup. |
| Redis | Multi-node-safe state (rate limits, nonces, sessions), per-service ACLs. |
| OpenBao | Secrets and cryptography — Transit signing, per-tenant envelope encryption, HMAC. |
Additional federation-member connectors (Entra ID, Okta, Google Workspace, and others) are optional and disabled by default; enable only the ones your tenants attach.
Two things are provisioned for you, so there are no manual post-install steps:
- Secrets and signing — a chart init Job (post-install / post-upgrade) idempotently
mounts the OpenBao Transit engine, creates the signing keys and the KV mount, and configures
Kubernetes auth. OpenBao runs with auto-unseal on persistent storage, so a backend pod
restart is a non-event (ADR
2026-06-07-vault-to-openbao.md). - Schema — Flyway applies the migrations on each service's startup; recovery is forward-patch-only (never drop-and-recreate).
Step 1 — Configure your environment values
The chart ships reference overlays you can start from: values-ci.yaml, values-staging.yaml,
and values-production.yaml (a structured production scaffold — the base values.yaml still
carries the shared defaults). Copy the closest overlay and set your
environment's hosts, registry, and secrets.
A few settings are required and fail the deploy loudly if unset — this is deliberate, so a misconfigured deploy stops at render time rather than shipping a broken or host-header-poisonable URL:
global.image.tag(or per-service<service>.image.tag) — an immutable image SHA.hub.publicBaseUrl— the hub's public issuer base, e.g.https://hub.<your-domain>.identity.publicBaseUrl— identity-service's public base, e.g.https://identity.<your-domain>(used to build password-reset URLs from config, never from request headers).apple.publicBaseUrl— only if you enable the Apple federation member.
The full oauthy.* configuration surface and every infrastructure knob are in the
Configuration reference.
Step 2 — Deploy the chart
Install (or upgrade) the release into a dedicated namespace:
helm upgrade --install thoryn ./deploy/helm/thoryn \
--namespace thoryn \
--create-namespace \
-f your-environment-values.yaml \
--set global.image.tag=<immutable-image-sha> \
--set hub.publicBaseUrl=https://hub.<your-domain> \
--set identity.publicBaseUrl=https://identity.<your-domain> \
--timeout 15mThe --timeout 15m gives the post-install init Job and the Spring services room for a cold
start. Your environment values file supplies the remaining per-environment secrets and
infrastructure settings (registry pull credentials, database, ingress hosts, TLS); the
production-grade shape of those is the subject of the forthcoming Operations runbooks.
Step 3 — Verify every service is Ready
Watch the database first, then the deployments. Kubernetes rollouts report Ready only once the service passes its readiness probe (the deep checks — OpenBao Transit, Redis ACL, JWKS, R2DBC):
# Database (StatefulSet) first — services depend on it and on Flyway completing.
kubectl rollout status statefulset/thoryn-postgres -n thoryn --timeout=10m
# Then the core services.
for d in thoryn-redis thoryn-vault thoryn-hub thoryn-api-gateway \
thoryn-product-api thoryn-identity-service; do
kubectl rollout status deployment/"$d" -n thoryn --timeout=10m
doneThe platform separates liveness (is the JVM up?) from readiness (are the deep dependencies healthy?). A transient OpenBao warm-up or Redis-ACL render delay flips readiness, not liveness, so a healthy pod is given time to become ready instead of being crash-looped. You can inspect either directly:
kubectl exec -n thoryn deploy/thoryn-hub -- \
wget -qO- http://localhost:8080/actuator/health/readinessStep 4 — Confirm the OIDC broker is serving
The definitive "it's up" check is the hub's discovery document. From inside the cluster:
kubectl exec -n thoryn deploy/thoryn-hub -- \
wget -qO- http://thoryn-hub:8080/.well-known/openid-configurationA JSON document naming issuer, authorization_endpoint, token_endpoint, and jwks_uri is
your first success — the broker is running and ready to integrate against. Externally, the
same document is served at your configured issuer (https://hub.<your-domain>/.well-known/openid-configuration)
once ingress and TLS resolve.
In-cluster probes use in-cluster URLs. When scripting health checks from another pod, dial the in-cluster Service (
http://thoryn-hub:8080/...), not the external ingress URL — it keeps your checks independent of ingress health.
You have a running platform — where to go next
Configuration is identical to the managed service from here:
- Integrate an application — register your first OAuth client against your issuer and drive the OIDC flow.
- Configuration reference — the complete
oauthy.*namespace and infrastructure knobs. - API reference — the customer-plane management API and its scope catalog.
- Operations runbooks (
docs/content/operate/, in progress) — production secret provisioning, DNS / TLS, upgrades, and backup / restore. Until these are published, the configuration reference and the chart's reference values files are the authoritative depth.
Troubleshooting
| Symptom | Cause / fix |
|---|---|
helm upgrade fails at render with a required message | A required setting is unset — global.image.tag (must be an immutable SHA), hub.publicBaseUrl, identity.publicBaseUrl, or apple.publicBaseUrl (if Apple is enabled). The message names the missing value. |
A pod is Running but never becomes Ready | A deep readiness dependency is DOWN (OpenBao Transit, Redis ACL, JWKS, R2DBC). Check .../actuator/health/readiness for the failing indicator; liveness staying up is expected — readiness gates traffic, not the process. |
| Services can't reach the database | The Postgres StatefulSet is not Ready yet, or Flyway is still applying migrations — wait for rollout status statefulset/thoryn-postgres to complete before expecting services to become Ready. |
A helm upgrade conflicts after a manual kubectl edit/patch | An emergency kubectl write transferred field ownership away from Helm. Prefer chart-driven changes; avoid hand-editing chart-managed resources. |
| Discovery returns 404 externally but works in-cluster | Ingress or TLS is not resolving yet — the in-cluster check (Step 4) isolates the broker from ingress; fix ingress / certificate issuance separately. |
Security and operational notes
- Rebuildable from git, zero manual steps. Every change ships as chart / migration / config
through a deploy; a clean from-scratch deploy must reproduce the working state. Do not
kubectl edit/patchchart-managed resources except as a back-filled emergency mitigation. - Signing keys never leave OpenBao. All token and audit signing is Transit-backed; a JVM heap dump cannot forge a signature. See Signing and crypto.
- Multi-node-safe by construction. Cross-request state (rate limits, nonces, sessions) lives in Redis, so the platform scales horizontally without split-brain.
- Forward-patch-only schema.
DROP DATABASE/TRUNCATE/ drop-and-re-bootstrap is forbidden in every environment, staging included — a CI guard enforces it. - The managed delivery model runs this same stack operated by Thoryn — the security properties above are identical (see Delivery-model architecture).