Skip to main content

Keycloak Common — Shared Application Configuration

Keycloak_Common is the shared application layer for Keycloak. It is not deployed on its own; instead it supplies the Keycloak-specific configuration that both Keycloak_GKE and Keycloak_CloudRun build on, so the two platform variants behave identically where it matters. End users never configure this layer directly — it has no deployment UI inputs of its own — but understanding what it provides explains the defaults you see in the platform docs.

For the infrastructure that actually provisions and runs Keycloak, see the platform guides (Keycloak_GKE, Keycloak_CloudRun) and the foundation guides (App_GKE, App_CloudRun, App_Common).


1. What this layer provides

AreaProvided by Keycloak_CommonWhere it surfaces
Container imagePins quay.io/keycloak/keycloak and builds a production-optimized custom variant (kc.sh buildstart --optimized) via a multi-stage Dockerfilecontainer_image output of the platform deployment
Custom entrypointMaps the foundation-injected DB_* env vars onto Keycloak's KC_DB_* variables, assembles the JDBC KC_DB_URL, and auto-detects the public URL for KC_HOSTNAMEApplication behaviour in the platform guides
Database engineFixes Cloud SQL for PostgreSQL 15 as the only supported engine§Database in the platform guides
Database bootstrapDefines the first-deploy db-init job that creates the role and database and grants schema privileges (idempotent, PostgreSQL 15+-aware)initialization_jobs output
SecretsCreates the bootstrap admin password secret in Secret Manager (KC_BOOTSTRAP_ADMIN_PASSWORD)secret_ids output
Core environmentInjects KC_DB=postgres, KC_PROXY_HEADERS=xforwarded, KC_HTTP_ENABLED, KC_HEALTH_ENABLED, KC_METRICS_ENABLED, KC_BOOTSTRAP_ADMIN_USERNAME=admin§Environment in the platform guides
Object storageNone — Keycloak stores all state in PostgreSQL (storage_buckets = [])storage_buckets output
Health checksSupplies TCP startup (30 s delay, 30 failures) and TCP readiness (60 s delay) probe defaults on port 8080 — Keycloak's /health lives on the unexposed management port 9000§Observability in the platform guides

2. Secrets in Secret Manager

Keycloak's only app-level secret is the bootstrap admin password — a 20-character random password stored as secret-<prefix>-keycloak-admin-password and injected into the container as KC_BOOTSTRAP_ADMIN_PASSWORD (username admin via KC_BOOTSTRAP_ADMIN_USERNAME). The database password is generated by the foundation and injected as DB_PASSWORD; the entrypoint maps it to KC_DB_PASSWORD at runtime.

# List the Keycloak secrets
gcloud secrets list --project "$PROJECT" --filter="name~keycloak"

# Retrieve the bootstrap admin password for first login
gcloud secrets versions access latest \
--secret="$(gcloud secrets list --project "$PROJECT" \
--filter='name~keycloak-admin-password' --format='value(name)' --limit=1)" \
--project "$PROJECT"

The bootstrap admin is temporary by design — log in at <service-url>/admin, create a permanent administrator, then remove or rotate the bootstrap user.


3. Container image and custom entrypoint

Keycloak_Common builds a custom image with Cloud Build (a cloudbuild.yaml + Dockerfile in scripts/). The multi-stage Dockerfile:

  1. Builder stage — runs /opt/keycloak/bin/kc.sh build with KC_DB=postgres, KC_HEALTH_ENABLED=true, and KC_METRICS_ENABLED=true, baking the DB vendor and features into a production-optimized build.
  2. Runtime stage — copies the optimized build, overlays /keycloak-entrypoint.sh (dropping back to the unprivileged UBI user 1000), and starts with kc.sh start --optimized so the slow build step never runs at boot.

The entrypoint performs these actions on every container start:

  1. JDBC URL assembly. Builds KC_DB_URL = jdbc:postgresql://<host>:<port>/<db> from the platform-injected DB_HOST/DB_PORT/DB_NAME. If DB_HOST is a Cloud SQL Unix-socket directory (starts with /), it falls back to DB_IP — the PostgreSQL JDBC driver cannot use Unix sockets, which is also why the Cloud Run variant defaults enable_cloudsql_volume = false.
  2. Credential mapping. Maps DB_USERKC_DB_USERNAME and DB_PASSWORDKC_DB_PASSWORD, only when not already set, so operators can override any KC_DB_* variable via environment_variables.
  3. Hostname auto-detection. Queries the Cloud Run metadata/Admin API for the service URL and exports it as KC_HOSTNAME (falling back to SERVICE_URL on GKE), with KC_HOSTNAME_STRICT=false behind the TLS-terminating front end.
  4. Launch. Prints a configuration summary and execs kc.sh start --optimized.
# Check the entrypoint's configuration summary in the logs
gcloud run services logs read <service-name> --project "$PROJECT" --region "$REGION" \
--limit 50 | grep -E "KC_DB_URL|KC_HOSTNAME|Keycloak"

4. Database engine and bootstrap

Keycloak requires PostgreSQL; the engine is fixed at POSTGRES_15 inside Keycloak_Common. On every apply a one-shot db-init job (postgres:15-alpine, up to 3 retries) idempotently:

  1. Creates the application role (or updates its password if it exists) and grants it to postgres so the superuser can manage its objects.
  2. Creates the Keycloak database owned by that role (or fixes ownership if it exists).
  3. Grants all privileges on the database and on SCHEMA public — required for PostgreSQL 15+, where public is no longer world-writable.
  4. Sends a POST /quitquitquit shutdown signal to the Cloud SQL Proxy sidecar so the Job exits cleanly on GKE.

Keycloak itself creates and migrates the schema on first boot. Inspect the database directly:

gcloud sql connect <instance-name> --user=<db-user> --project "$PROJECT"

The instance, database, and user names are in the platform deployment outputs (tenant-prefixed at deploy time).


5. Core environment defaults

Keycloak_Common establishes the baseline environment so Keycloak comes up correctly in production mode behind a TLS-terminating front end:

  • KC_DB = postgres — the DB vendor, also baked into the optimized build.
  • KC_PROXY_HEADERS = xforwarded and KC_HTTP_ENABLED = true — Cloud Run / the GKE load balancer terminate TLS and forward X-Forwarded-* headers; Keycloak speaks plain HTTP on 8080 behind them.
  • KC_HEALTH_ENABLED = true and KC_METRICS_ENABLED = true — expose /health and /metrics on the management port 9000.
  • KC_BOOTSTRAP_ADMIN_USERNAME = admin — paired with the Secret Manager password above.
  • KC_DB_URL, KC_DB_USERNAME, KC_DB_PASSWORD, and KC_HOSTNAME are resolved at runtime by the entrypoint, never hardcoded — the foundation tenant-prefixes the real role and database names, so hardcoding them would authenticate as a non-existent role.

6. Health probe behaviour

Keycloak 25+ serves /health, /health/ready, /health/live, and /metrics on the separate management port 9000 — not on the HTTP port 8080 the platforms probe. An HTTP probe against 8080/health would always 404, so the defaults are TCP checks that confirm the HTTP listener is accepting connections:

  • Startup probe — TCP on 8080, initial delay 30 s, period 10 s, failure threshold 30 (up to ~330 s for JVM start + first-boot schema migrations).
  • Readiness probe — TCP on 8080, initial delay 60 s, period 15 s, failure threshold 3.
  • Liveness — the Cloud Run variant overrides to HTTP / (60 s delay), which answers on 8080 once Keycloak is up.

Do not tighten the startup budget: on first boot Keycloak must connect to PostgreSQL and create its entire schema before the listener opens.


7. Object storage

None. Keycloak keeps realms, clients, users, and sessions entirely in PostgreSQL — Keycloak_Common declares storage_buckets = [] and no GCS volumes, so a database backup captures the full deployment state.


For the Keycloak-specific, user-facing configuration (variables by group, outputs, and how to explore each service from the Console and CLI), see the platform guides: Keycloak_GKE and Keycloak_CloudRun.