Production Checklist
From start-dev to production
Section titled “From start-dev to production”start-dev mode exists only for local development. It disables the optimised build, uses an embedded H2 database, generates a self-signed certificate, and relaxes hostname validation. None of these defaults are safe in production. When you are ready to go live, there are four things you must address.
The four production must-haves
Section titled “The four production must-haves”1. External database
Section titled “1. External database”By default, start-dev uses an embedded H2 database that lives inside the container. Restart the container and all data is gone. In production, point Keycloak at an external database — PostgreSQL is the recommended choice.
Set these environment variables:
KC_DB=postgresKC_DB_URL=jdbc:postgresql://db-host:5432/keycloakKC_DB_USERNAME=keycloakKC_DB_PASSWORD=<secret>2. Fixed hostname
Section titled “2. Fixed hostname”Keycloak embeds its own hostname into token issuers, OIDC discovery documents, and redirect validation. If the hostname is wrong, tokens are rejected by clients. Set it explicitly:
KC_HOSTNAME=auth.example.comFor local testing behind a reverse proxy, you may also need KC_HOSTNAME_STRICT=false, but never use that flag in production.
3. HTTPS and reverse proxy
Section titled “3. HTTPS and reverse proxy”Keycloak must be served over HTTPS. You have two common options:
-
Edge termination (recommended): A reverse proxy (Nginx, Traefik, AWS ALB) handles TLS and forwards plain HTTP to Keycloak. Tell Keycloak to trust the proxy headers:
KC_PROXY_HEADERS=xforwarded -
Passthrough: Keycloak handles TLS itself. Provide a certificate and private key via
KC_HTTPS_CERTIFICATE_FILEandKC_HTTPS_CERTIFICATE_KEY_FILE.
4. Optimised build
Section titled “4. Optimised build”In production, run kc.sh build before kc.sh start. The build step compiles provider configuration, augments the Quarkus app, and caches classpath scanning — reducing startup time from ~30 s to ~3 s. Most Docker deployments do this in a multi-stage Dockerfile:
# Build stagekc.sh build --db=postgres
# Runtime stagekc.sh start --optimizedA production docker run
Section titled “A production docker run”Here is a minimal docker run that combines all four must-haves:
docker run -d --name keycloak \
-p 8080:8080 \
-e KC_DB=postgres \
-e KC_DB_URL=jdbc:postgresql://db:5432/keycloak \
-e KC_DB_USERNAME=keycloak \
-e KC_DB_PASSWORD=${DB_PASSWORD} \
-e KC_HOSTNAME=auth.example.com \
-e KC_PROXY_HEADERS=xforwarded \
-e KEYCLOAK_ADMIN=admin \
-e KEYCLOAK_ADMIN_PASSWORD=${ADMIN_PASSWORD} \
quay.io/keycloak/keycloak:latest \
start --optimizedPass DB_PASSWORD and ADMIN_PASSWORD from your secrets manager — never hardcode them in the command.
Config-as-code with realm export/import
Section titled “Config-as-code with realm export/import”Manual admin-console clicks are not repeatable. Export your realm configuration as JSON and commit it to version control:
# Export a realm/opt/keycloak/bin/kc.sh export --realm my-app --file /tmp/my-app-realm.json
# Import on startup/opt/keycloak/bin/kc.sh start --import-realmPlace my-app-realm.json in /opt/keycloak/data/import/ and Keycloak will import it automatically on first start. This makes realm configuration reproducible across environments.
Backup strategy
Section titled “Backup strategy”Keycloak’s state lives entirely in the external database. Back up the database using your database provider’s standard backup mechanism. The container itself is stateless and does not need to be backed up — just ensure the realm export JSON is in version control as a configuration baseline.