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
opensslcommand-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 three separate databases:
| Database | Purpose |
|---|---|
config | Application configuration, flows, and identity providers |
runtime | Active sessions, tokens, and OAuth state |
user | User accounts and credentials |
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:
type: "postgres"
postgres:
hostname: "your-db-host"
port: "5432"
name: "runtimedb"
username: "dbuser"
password: "dbpassword"
sslmode: "require"
max_open_conns: 500
max_idle_conns: 100
conn_max_lifetime: 3600
user:
type: "postgres"
postgres:
hostname: "your-db-host"
port: "5432"
name: "userdb"
username: "dbuser"
password: "dbpassword"
sslmode: "require"
max_open_conns: 500
max_idle_conns: 100
conn_max_lifetime: 3600
Key settings:
- Set
sslmodeto"require"to enforce encrypted connections to the database. - Adjust
max_open_conns,max_idle_conns, andconn_max_lifetimebased on your expected traffic and database capacity. - Never store database credentials in
deployment.yamldirectly. Use environment variables, Kubernetes secrets, or a secrets manager.
Replace TLS Certificates
ThunderID ships with a self-signed TLS certificate for local development. Self-signed certificates trigger browser warnings and are not trusted by clients in production. Replace them with certificates issued by a trusted CA.
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 repository/resources/security/server.cert
cp your-domain.key repository/resources/security/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: "repository/resources/security/server.cert"
key_file: "repository/resources/security/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/repository/conf/deployment.yaml \
-v $(pwd)/certs/server.cert:/opt/thunder/repository/resources/security/server.cert \
-v $(pwd)/certs/server.key:/opt/thunder/repository/resources/security/server.key \
ghcr.io/thunder-id/thunder-id:latest
Generate a Unique Encryption Key
ThunderID uses a symmetric encryption key to protect sensitive data at rest, including credentials and tokens. The default key in the repository is a well-known value that anyone can find. You must replace it before going to production.
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>" > repository/resources/security/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://repository/resources/security/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>"
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.
Configure a CORS Allowlist
Cross-Origin Resource Sharing (CORS) controls which origins can make requests to ThunderID from a browser. The default configuration allows only https://localhost:3000. In production, you must explicitly list every origin that legitimately needs access.
Update deployment.yaml with your allowed origins:
cors:
allowed_origins:
- "https://app.example.com"
- "https://admin.example.com"
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:
cors:
allowed_origins:
- "https://app.example.com"
- regex: '^https://[a-z0-9-]+\.example\.com$'Regex patterns must be fully anchored — start with
^and end with$. ThunderID logs a warning at startup for any pattern that is not fully anchored, because unanchored patterns match more origins than intended.
Configure Caching
ThunderID supports two cache backends: in-memory and Redis.
| Cache Type | Suitable For |
|---|---|
| In-memory | Single-pod deployments only |
| Redis | Multi-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
keyPrefixwhen 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.yamland therepository/resources/security/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.