Skip to content

Keycloak auf Azure — Prod-Deployment-Design

Design-Dokument für den produktiven Keycloak unter auth.targetshot.app und auth-admin.targetshot.app. Staging bleibt unverändert auf dem bestehenden on-prem Docker-Setup hinter HAProxy.

Zielbild

  • Kein OS-Patching: Managed Compute via Azure App Service Linux Container, nicht Azure VM.
  • Admin-Only-Intranet-Schutz: Pfad-basierte App-Service Access Restrictions ersetzen die haproxy-Regeln für /admin/* und /realms/master/*. Ein Keycloak-Worker deckt beide Hostnames ab — kein Cluster-Setup nötig.
  • Managed Postgres: Azure Database for PostgreSQL Flexible Server in einem delegated Subnet, Private Endpoint, kein Public Access.
  • Kleine Baseline, vertikal skalierbar: B2 App-Service-Plan + B1ms Postgres. Hochstufen im Portal ohne Redesign.

Komponenten

Ressource SKU / Config Zweck
Resource Group targetshot-kc in Germany West Central Kapselung
App Service Plan Linux B2 (2 vCPU, 3.5 GB) Compute für Keycloak
Web App keycloak-prod, Docker Container aus ACR Keycloak-Instanz
Azure Container Registry Basic (targetshotregistry oder ähnlich) Eigenes Keycloak-Image mit Themes
Postgres Flex Standard_B1ms, 32 GB SSD, Burstable KC-Datenbank
VNet targetshot-kc-vnet, 10.20.0.0/16 Private Netzwerk
Subnet app 10.20.1.0/24, delegated to Microsoft.Web/serverFarms App Service Regional VNet Integration
Subnet db 10.20.2.0/24, delegated to Microsoft.DBforPostgreSQL/flexibleServers PG Flex Private Endpoint
Private DNS Zone privatelink.postgres.database.azure.com Namensauflösung für PG intern

Domains & DNS

Zwei Hostnames zeigen auf dieselbe App Service, Trennung passiert erst bei den Access Restrictions.

Cloudflare-Records

Typ Name Inhalt Proxy
CNAME auth keycloak-prod.azurewebsites.net DNS only
CNAME auth-admin keycloak-prod.azurewebsites.net DNS only
TXT asuid.auth Verification-ID aus Azure
TXT asuid.auth-admin gleiche Verification-ID

DNS-only, nicht proxied. Wenn Cloudflare proxy't, sieht App Service die CF-Edge-IP und unsere Pfad-IP-Regeln fallen zusammen. Später kann Azure Front Door die DDoS-/WAF-Rolle übernehmen.

Azure-Domain-Binding

  1. App Service → Custom domains → Add custom domain → auth.targetshot.app.
  2. Verification-ID an Cloudflare als asuid.auth TXT-Record.
  3. Validate → Azure bindet die Domain an den Web App.
  4. „Create App Service Managed Certificate" klicken → Azure macht HTTP-01-Challenge über den CNAME, Cert lebt so lange die Domain gebunden ist und wird automatisch erneuert.
  5. Dieselbe Prozedur für auth-admin.targetshot.app.

Access Restrictions (admin-only-intranet)

Regeln am App Service, ausgewertet in Priority-Reihenfolge (niedriger = zuerst). Die Allow-Regeln sind also erste Wahl, dann greifen die Denies als Fallback.

Priority Path filter Action Source
50 /admin/* Allow Business-IP (93.241.78.8/32) + weitere Allowlist-IPs
51 /realms/master/* Allow dieselbe Allowlist
100 /admin/* Deny 0.0.0.0/0
101 /realms/master/* Deny 0.0.0.0/0
1000 Allow (default) 0.0.0.0/0

Ergebnis: /realms/<normal>/protocol/openid-connect/... ist für jeden erreichbar, /admin/* und /realms/master/* nur aus der Allowlist. Keycloak setzt via KC_HOSTNAME_ADMIN die Admin-Konsole auf auth-admin.targetshot.app, aber die Security liegt auf Pfad-Ebene — dadurch ist egal ob jemand die Admin-Hostname-Variante rät.

Keycloak-Konfiguration

Env-Vars in der Web App (oder via az webapp config appsettings):

KC_DB=postgres
KC_DB_URL=jdbc:postgresql://targetshot-kc.privatelink.postgres.database.azure.com:5432/keycloak?sslmode=require
KC_DB_USERNAME=keycloak
KC_DB_PASSWORD=<Key Vault Reference>
KC_HOSTNAME=auth.targetshot.app
KC_HOSTNAME_ADMIN=auth-admin.targetshot.app
KC_HOSTNAME_STRICT=true
KC_PROXY_HEADERS=xforwarded
KC_HOSTNAME_BACKCHANNEL_DYNAMIC=true
KC_HTTP_ENABLED=true
KC_HEALTH_ENABLED=true
KC_METRICS_ENABLED=true
KC_THEME_CACHE_ENABLED=true
WEBSITES_PORT=8080

KC_HTTP_ENABLED=true weil App Service TLS selbst terminiert und den Container per HTTP anspricht. KC_PROXY_HEADERS=xforwarded sorgt dafür, dass Keycloak die echte Client-IP aus dem Forwarded-Header liest.

Passwort gehört in Azure Key Vault; die Web App nutzt Managed Identity + eine Key-Vault-Reference (@Microsoft.KeyVault(...)).

Image-Pipeline

Themes + ggf. Providers werden ins Image gebaut, nicht gemountet. Theme-Änderungen sind selten, Bind-Mounts auf App Service wären Azure Files = SMB = Mehraufwand ohne Gegenwert.

  1. infrastructure/keycloak/Dockerfile erbt von quay.io/keycloak/keycloak:<version>, kopiert infrastructure/keycloak/themes/ nach /opt/keycloak/themes/ und ruft kc.sh build für das Production-Image auf.
  2. GitHub Action baut das Image bei Push auf infrastructure/keycloak/**, pusht nach ACR als ghcr.io/targetshot-app/keycloak:<sha>.
  3. az webapp config container set setzt den Tag auf der Web App um; App Service zieht das neue Image und startet den Container neu.

Staging kann dieselbe Image-Pipeline mit anderen Tags nutzen, läuft aber weiterhin auf dem on-prem Host — dort bleibt Docker-Compose mit Bind-Mount für themes (einfacher Wechsel ohne Rebuild in Staging).

Datenbank-Initialisierung

  1. PG Flex mit Admin-User pgadmin anlegen.
  2. Einmalig per psql (vom App-Subnet aus, temporärer Jump-Container) CREATE DATABASE keycloak; CREATE USER keycloak WITH PASSWORD ...; GRANT ALL ON DATABASE keycloak TO keycloak; ausführen.
  3. Passwort in Key Vault als Secret ablegen.
  4. Web-App zieht Credentials via Managed Identity + Key Vault Reference.
  5. Keycloak startet, führt Liquibase-Schema-Migrationen beim ersten Boot aus.

Realm-Import

Bestehende Realms aus dem aktuellen Staging-Keycloak exportieren (kc.sh export --dir /tmp/export --realm <realm>), ins neue Image kopieren oder beim ersten Start per KC_IMPORT_REALM einspielen. Realm-Export ist idempotent; User-Migrations separat planen (Postgres Dump + Restore ist einfacher als Realm-Level-Export für User).

Scaling-Pfad

Achse Schritt Operativ
Compute vertikal B2 → S1 → P0v3 → P1v3 „Scale up" im Portal, ohne Downtime auf gleichem Plan
DB vertikal B1ms → B2s → D2ds_v4 Reboot-Fenster
Compute horizontal ab S1 möglich Keycloak-Cluster via Infinispan JDBC-Ping konfigurieren
Multi-Region Azure Front Door + Second App Failover-Policy, eigenes Doc

Kosten (Germany West Central, ca., pay-as-you-go)

Posten Monatlich
App Service Plan Linux B2 ~€25
Postgres Flex B1ms + 32 GB ~€18
Azure Container Registry Basic ~€4
Egress (<100 GB/mo) ~€0
VNet / Subnets €0
Managed TLS Cert €0
Summe ~€47 / Monat

Was dieses Dokument noch nicht enthält

  • Bicep- oder Terraform-IaC-Dateien — nächster Schritt.
  • GitHub-Actions-Workflow für Image-Build und App-Service-Deployment.
  • Realm-Migrations-Runbook (Export/Import, User-Dump).
  • Rollback-Plan, falls der Cutover scheitert (DNS-TTL, Fallback auf bestehendes on-prem Staging-Keycloak).

Diese Themen kommen in eigene Runbooks, sobald die IaC-Basis steht.