Deployment Options
Three ways to run the RealmSSO server, from a quick evaluation to a production Kubernetes fleet.
Docker Compose
The repo's docker-compose.yml wires Postgres 16, Redis 7, Keycloak 26, and the server together with a shared set of environment values. It is built for local development, not production, as shipped: Keycloak runs with start-dev --import-realm and KC_HTTP_ENABLED=true (no TLS), and the server container bind-mounts ./src and ./prisma and runs via tsx rather than a built image.
Before using Compose in production
Switch Keycloak's command to start (not start-dev), configure a real KC_HOSTNAME and TLS termination, remove the source bind mounts, build the server image from docker/Dockerfile instead of running it through tsx, and replace every change-me secret — see Configuration.
Standalone Docker image
docker/Dockerfile is a two-stage node:20-alpine build. The builder stage installs dependencies, generates the Prisma client, and compiles TypeScript; the final stage installs only production dependencies, copies just the compiled output and Prisma client, runs as the non-root node user, exposes port 4000, and carries a built-in HEALTHCHECK against GET /health every 30 seconds.
This is a good baseline for running on any container platform — plain docker run, ECS, Cloud Run — as long as you provide Postgres, Redis, and a Keycloak instance yourself and inject configuration as environment variables. One thing to wire up yourself: the image's HEALTHCHECK is the dependency-free /health, which is the right choice for a restart signal but tells your load balancer nothing about whether the database is reachable. If your platform has a separate readiness or target-group health check, point that one at /readyz instead, which does check Postgres and Redis.
Kubernetes (Helm chart)
helm/realmsso-server is a standard Helm chart with optional Bitnami postgresql (~15.5) and redis (~19.6) subcharts, each gated by postgresql.enabled / redis.enabled — both default to false, so the chart expects externally-managed Postgres and Redis (postgresql.externalUrl, redis.externalUrl) unless you turn them on.
| Values key | Default | What it controls |
|---|---|---|
| replicaCount | 1 | Base replica count (overridden by HPA minReplicas only when autoscaling is enabled) |
| image.repository / tag | realmsso/server / latest | Container image |
| ingress.* | nginx class, cert-manager annotations, a placeholder api.* host | Example host — replace before installing |
| autoscaling.* | off by default; when enabled: min 1 / max 10, 70% CPU, 80% memory | HorizontalPodAutoscaler — opt-in |
| resources.* | 1 CPU / 1Gi limit, 250m / 256Mi request | Container resource requests and limits |
| podSecurityContext / securityContext | non-root uid 1000, all capabilities dropped, read-only root filesystem | Pod and container hardening |
Both the liveness and readiness probes currently point at the dependency-free /health endpoint — see Helm Chart Reference for the note on pointing readiness at /readyz instead.
Secrets
secret.yaml templates one Secret from DATABASE_URL, REDIS_URL, KEYCLOAK_ADMIN_URL, KEYCLOAK_ADMIN_CLIENT_SECRET, JWT_SECRET, MAGIC_LINK_SECRET, ENCRYPTION_KEY, and the three SMTP_* values. Five of those have no usable default and are wrapped in required, so the render fails outright rather than deploying an empty secret. Set existingSecret to the name of a Secret you manage yourself (external-secrets, kubectl create secret) to skip this templating entirely — that mode also skips every required guard, so a key you forget surfaces as a CrashLoopBackOff instead of a Helm error. See Helm Chart Reference for how to generate the key material.
The chart is not published to a public Helm repository, so install it from the chart directory in the server repo. Put the non-secret overrides in a values file — every hostname in it is yours, so substitute your own:
# my-values.yaml
postgresql:
externalUrl: postgresql://realmsso:PASS@db.example.com:5432/realmsso
redis:
externalUrl: redis://redis.example.com:6379
keycloak:
url: http://keycloak.keycloak.svc.cluster.local:8080 # in-cluster admin API
config:
dashboardBaseUrl: https://app.example.com
adminPortalBaseUrl: https://app.example.com
corsOrigins: https://app.example.com
smtp:
from: noreply@example.com
ingress:
hosts:
- host: api.example.com
paths:
- path: /
pathType: Prefix# Generate key material ONCE and keep it in your secret manager. Rotating
# jwtSecretKey invalidates every live vendor session, and rotating
# encryptionKey leaves already-stored client secrets undecryptable.
helm upgrade --install realmsso ./helm/realmsso-server -f my-values.yaml \
--set keycloakAdminClientSecret="$KC_ADMIN_SECRET" \
--set jwtSecretKey="$JWT_SECRET" \
--set magicLinkSecretKey="$MAGIC_LINK_SECRET" \
--set encryptionKey="$ENCRYPTION_KEY"config.dashboardBaseUrl is where magic-link emails send people — the chart deliberately ships it empty and refuses to render without it once ingress is enabled, rather than defaulting to a plausible-looking wrong hostname. See Helm Chart Reference for the other values worth overriding.
backup.* values are not yet wired to anything
values.yaml declares a backup block (enabled, schedule, retention), but no CronJob in the chart reads these values yet. Set up your own Postgres backup strategy rather than assuming this block does it for you; see Backup & Recovery.
monitoring.enabled is wired, but narrowly: when set, the Deployment gets prometheus.io/scrape pod annotations for annotation-based discovery. There is no Prometheus Operator ServiceMonitor template — if your cluster's Prometheus relies on the Operator CRD instead of annotation scraping, you will need to add one yourself.
Choosing an option
Use Docker Compose to evaluate RealmSSO or develop against it locally. Use the standalone image if you already run Postgres, Redis, and Keycloak and just need one more container. Use the Helm chart for a production Kubernetes deployment that needs autoscaling, ingress, and secret-manager integration out of the box.
Next steps
See Configuration for every environment variable, the Self-Hosting Guide for the Keycloak privileges self-hosting requires, and Monitoring & Observability for what the server exposes once it's running.