Skip to main content

Production Deployment Guidelines

This guide covers the critical configuration changes you must make before deploying ThunderID in a production environment. The default configuration is designed for local development and evaluation only. Do not use it in production without applying the changes below.

Prerequisites

Before you begin, ensure you have:

  • A running ThunderID instance configured for your deployment target (see Choose Your Deployment).
  • Access to a supported production database (PostgreSQL 13 or later recommended).
  • Valid TLS certificates issued by a trusted Certificate Authority (CA).
  • The openssl command-line tool installed on your system.

Configure a Production Database

The default configuration uses SQLite, which stores data in local files. SQLite does not support concurrent writes from multiple processes and is not suitable for production use. Use PostgreSQL for production deployments.

ThunderID uses four separate databases:

DatabasePurpose
configApplication configuration, flows, and identity providers
runtime_transientShort-lived OAuth state: authorization codes, requests, and nonces
entityUser accounts and credentials
runtime_persistentLong-lived operational state: revoked tokens, SSO sessions, and consents

Configure each database in deployment.yaml:

database:
config:
type: "postgres"
postgres:
hostname: "your-db-host"
port: "5432"
name: "configdb"
username: "dbuser"
password: "dbpassword"
sslmode: "require"
max_open_conns: 500
max_idle_conns: 100
conn_max_lifetime: 3600

runtime_transient:
type: "postgres"
postgres:
hostname: "your-db-host"
port: "5432"
name: "runtime_transient"
username: "dbuser"
password: "dbpassword"
sslmode: "require"
max_open_conns: 500
max_idle_conns: 100
conn_max_lifetime: 3600

entity:
type: "postgres"
postgres:
hostname: "your-db-host"
port: "5432"
name: "entitydb"
username: "dbuser"
password: "dbpassword"
sslmode: "require"
max_open_conns: 500
max_idle_conns: 100
conn_max_lifetime: 3600

runtime_persistent:
type: "postgres"
postgres:
hostname: "your-db-host"
port: "5432"
name: "runtime_persistent"
username: "dbuser"
password: "dbpassword"
sslmode: "require"
max_open_conns: 500
max_idle_conns: 100
conn_max_lifetime: 3600

Key settings:

  • Set sslmode to "require" to enforce encrypted connections to the database.
  • Adjust max_open_conns, max_idle_conns, and conn_max_lifetime based on your expected traffic and database capacity.
  • Never store database credentials in deployment.yaml directly. Use environment variables, Kubernetes secrets, or a secrets manager.

Replace TLS Certificates

For quickstart and development, ThunderID generates a self-signed TLS certificate per deployment during setup. Self-signed certificates trigger browser warnings and are not trusted by clients in production. Replace the server certificate with one issued by a trusted CA, or terminate TLS at your ingress or load balancer with a CA-issued certificate.

Step 1: Get a TLS Certificate

Get a certificate and private key for your production domain from a trusted CA. You can use:

  • Let's Encrypt for free, automated certificates.
  • Your organization's internal CA.
  • A commercial CA.

Step 2: Replace the Default Certificates

Copy your certificate and private key to the ThunderID security directory:

cp your-domain.cert config/certs/server.cert
cp your-domain.key config/certs/server.key

Step 3: Update the Configuration

Verify the paths in deployment.yaml point to the correct files:

tls:
min_version: "1.3"
cert_file: "config/certs/server.cert"
key_file: "config/certs/server.key"

If you store certificates in a different location, update cert_file and key_file accordingly.

When deploying with Docker, mount the certificates as volumes:

docker run --rm \
-p 8090:8090 \
-v $(pwd)/deployment.yaml:/opt/thunder/deployment.yaml \
-v $(pwd)/certs/server.cert:/opt/thunder/config/certs/server.cert \
-v $(pwd)/certs/server.key:/opt/thunder/config/certs/server.key \
ghcr.io/thunder-id/thunderid:latest

Generate a Unique Encryption Key

ThunderID uses a symmetric encryption key to protect sensitive data at rest, including credentials and tokens, along with separate keys to sign JWTs. During setup, each deployment generates its own unique key material. For production, either keep the generated per-deployment keys (ensuring they are persisted and backed up) or provide and manage your own through a secure process such as a secrets manager. The steps below show how to provide your own encryption key.

Step 1: Generate a New Key

Generate a cryptographically secure 32-byte key:

openssl rand -hex 32

This command outputs a 64-character hexadecimal string. Copy the output.

Step 2: Store the Key

Write the generated value to the key file:

printf '%s' "<generated-key>" > config/certs/crypto.key

Replace <generated-key> with the output from the previous command. Do not include whitespace or newline characters.

Step 3: Verify the Configuration

Confirm deployment.yaml references the key file:

crypto:
encryption:
key: "file://config/certs/crypto.key"

You can also supply the key directly as an inline value, but this is not recommended for production. Prefer the file:// path form, an environment variable, or a secrets manager to avoid storing the key in deployment.yaml:

crypto:
encryption:
key: "<generated-key>"
warning

Protect the encryption key with the same level of care as a database password. If the key is lost, encrypted data becomes unrecoverable. If the key is compromised, all protected data is exposed. This key material lives in config/certs (the certs PVC on Helm) and must persist across restarts and replicas. If it is lost or changed, previously encrypted data cannot be decrypted and previously issued tokens can no longer be validated.

Provide the Direct Auth Secret

ThunderID gates the Direct API endpoints (/auth/**, /register/passkey/**) and the protected AuthZEN access endpoints (/access/**) with a server-level Direct Auth Secret. During setup, each deployment generates its own secret. The default deployment.yaml references it as a file, so the value stays out of the config file:

server:
security:
direct_auth_secret: "file://config/secrets/direct_auth_secret"

For production, either keep the generated per-deployment secret (ensuring config/secrets is persisted) or provide your own. To provide your own, generate a strong value and write it to the secret file:

openssl rand -hex 32
printf '%s' "<generated-secret>" > config/secrets/direct_auth_secret

Replace <generated-secret> with the output from the previous command. Do not include whitespace or newline characters. Callers must send this value in the Direct-Auth-Secret header. You can also supply the secret directly as an inline value, but prefer the file:// path form or a secrets manager to keep it out of deployment.yaml.

Configure a CORS Allowlist

Cross-Origin Resource Sharing (CORS) controls which browser origins can call ThunderID. Origins are stored in the server-config cors section. No origins are allowed by default, so list each production app explicitly.

Create config/resources/server_configs/cors.yaml:

name: cors
value:
allowedOrigins:
- "https://app.example.com"
- "https://admin.example.com"

To change origins without a restart, update the same section with PUT /server-config/cors. Runtime values are merged with the declarative file.

Guidelines:

  • List only origins that your applications actively use. Do not use wildcards (*).

  • Use the full origin format: scheme, hostname, and port if non-standard (for example, https://app.example.com:8443).

  • If your domain follows a consistent pattern, you can use a regular expression:

    name: cors
    value:
    allowedOrigins:
    - "https://app.example.com"
    - regex: '^https://[a-z0-9-]+\.example\.com$'

    Anchor regex patterns with ^ and $. ThunderID logs a startup warning for unanchored patterns because they can match unintended origins.

Configure Caching

ThunderID supports two cache backends: in-memory and Redis.

Cache TypeSuitable For
In-memorySingle-pod deployments only
RedisMulti-pod (replicated) deployments

Do not use in-memory caching when running multiple replicas. Each pod maintains a separate cache, which causes cache inconsistency across the deployment. Cached entries written in one pod are invisible to other pods, leading to incorrect authentication or authorization decisions.

Single-Pod Deployments

For a single-pod deployment, in-memory caching is acceptable:

cache:
disabled: false
type: "inmemory"
size: 1000
ttl: 3600
evictionPolicy: "LRU"
cleanupInterval: 300

Multi-Pod Deployments

Use Redis when running more than one replica:

cache:
disabled: false
type: "redis"
ttl: 3600
redis:
address: "your-redis-host:6379"
username: ""
password: "your-redis-password"
db: 0
keyPrefix: "thunderid:"

Key settings:

  • Set a keyPrefix when multiple services share the same Redis instance. This avoids key collisions.
  • Secure Redis with authentication and network-level access controls. Do not expose Redis publicly.
  • Use a managed Redis service (such as Amazon ElastiCache or Google Memorystore) for high availability.

Next Steps

After applying the production configuration:

  • Restrict access to deployment.yaml and the config/certs/ directory. These files contain sensitive credentials and keys.
  • Enable database backups on a regular schedule.
  • Set up monitoring and alerting for ThunderID and its dependent services.
  • Review the Kubernetes deployment guide for Helm-based configuration of the settings covered here.

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.