Skip to content

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 kubectl configured for it.
  • Helm v3.
  • The Thoryn Helm chart, at deploy/helm/thoryn in this repository (chart thoryn).
  • 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:

ComponentRole
authorization-hubThe OAuth 2.0 / OIDC broker — issues tokens; owns no user data.
product-apiThe customer-plane management API (tenants, applications, federation, audit).
api-gatewayPublic ingress for product-api (TLS, edge rate limiting, bearer validation).
identity-serviceThoryn's own OIDC provider — users, login UI, SCIM — as a federation member.
Postgres (StatefulSet)Platform database; Flyway migrations run on service startup.
RedisMulti-node-safe state (rate limits, nonces, sessions), per-service ACLs.
OpenBaoSecrets 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 15m

The --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
done

The 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/readiness

Step 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-configuration

A 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

SymptomCause / fix
helm upgrade fails at render with a required messageA 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 ReadyA 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 databaseThe 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/patchAn 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-clusterIngress 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 / patch chart-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).