Wenn man im Netz nach Anleitungen zur Bereitstellung von Keycloak sucht, findet man häufig nur Testkonfigurationen oder „Quickstart“-Szenarien. Doch wie sieht es aus, wenn man Keycloak etwas näher an eine produktionsfähige Konfiguration bringen will? In diesem Artikel zeige ich Dir Schritt für Schritt, wie Du Keycloak in Azure Container Instances (ACI) deployen kannst. Natürlich decken wir nicht alle Aspekte einer produktiven Umgebung ab – dennoch gehen wir einige wichtige Punkte durch, um eine solide Basis zu schaffen.
Architekturübersicht

Die Architektur dieser Keycloak-Implementierung basiert auf einer klassischen Drei-Schichten-Struktur:
- Proxy Layer: Diese Schicht dient als Eingangsportal, über das alle Anfragen von außen geleitet werden. Hier setzen wir auf nginx, der Anfragen auf TLS/443 entgegennimmt und sicher an die Keycloak-Instanz weiterleitet. Alternativ kann ein verwalteter Dienst wie der Azure Application Gateway verwendet werden
- Application Layer: In dieser Schicht liegt die Keycloak-Instanz, die in einer Azure Container Instance (ACI) bereitgestellt wird. Keycloak authentifiziert und autorisiert Nutzer und bietet eine zentrale Verwaltung von Identitäten.
- Data Layer: Hier wird eine PostgreSQL-Datenbank verwendet, die entweder als verwalteter Dienst (z. B. Azure Database for PostgreSQL) oder innerhalb einer VM betrieben wird. Diese speichert alle Benutzerdaten und Konfigurationen von Keycloak
Zusätzlich setzen wir Azure Key Vault ein, um sensible Informationen wie Passwörter sicher zu speichern und in den verschiedenen Schichten bereitzustellen. Zwischen dem Load Balancer (im Proxy Layer) und Keycloak (im Application Layer) wird HTTP verwendet. Dies hat den Vorteil, dass keine unnötige TLS-Verschlüsselung innerhalb des VNets stattfindet, was die Latenz minimiert und die Performance verbessert. Allerdings könnte auch diese Strecke mit TLS abgesichert werden, wenn es die Unternehmensrichtlinien erfordern. Diese Entscheidung hängt stark von den spezifischen Sicherheitsanforderungen der Organisation ab. Alle Komponenten befinden sich in einem Azure Virtual Network (VNet), das die Kommunikation zwischen den Schichten isoliert und absichert.
Abgrenzungen und weiterführende Überlegungen
Dieses Setup stellt eine solide Basis dar, auf der Du aufbauen kannst. Für ein vollständig produktives Deployment müssten jedoch noch einige weitere Aspekte berücksichtigt werden, wie etwa Ausfallsicherheit, erweiterte Sicherheit oder Skalierung, die hier den Rahmen sprengen würden:
- Ausfallsicherheit (High Availability, Failover): In der aktuellen Konfiguration ist keine Redundanz vorgesehen. Für ein produktives Setup sollte die Keycloak-Instanz in einem Cluster betrieben werden, um einen Single Point of Failure zu vermeiden
- Web Application Firewall (WAF): Eine WAF schützt vor Angriffen wie SQL-Injection oder Cross-Site Scripting. In einem produktiven Umfeld sollte ein Azure Application Gateway mit aktivierter WAF in Betracht gezogen werden
- TLS zwischen den Schichten: Während HTTP zwischen Load Balancer und Keycloak aus Performance-Gründen verwendet wird, könnten Unternehmensrichtlinien eine durchgehende TLS-Verschlüsselung verlangen. Dies lässt sich je nach Bedarf leicht anpassen
- Skalierung: Für mehr Benutzerlast sollte die Architektur um eine automatische Skalierung erweitert werden, z. B. durch Verwendung von Kubernetes oder Azure Container Apps
- Backup und Wiederherstellung: Die PostgreSQL-Datenbank sollte mit einem automatisierten Backup-Plan geschützt werden, um Datenverlust zu vermeiden
- Monitoring und Alerts: Tools wie Azure Monitor oder Prometheus können eingesetzt werden, um die Performance und Verfügbarkeit der Keycloak-Instanz zu überwachen.
- DNS-Hostnamen: Im Blogpost wird ein automatisch generierter Hostname wie *.westeurope.azurecontainer.io verwendet. Dieser eignet sich nicht für ein produktives Setup, da produktive Umgebungen in der Regel einen passenden DNS-Eintrag mit einer eigenen Domain erfordern. Solche Hostnamen sind einfacher zu merken, wirken professioneller und erlauben eine bessere Kontrolle
Diese Aspekte sind essenziell für ein produktives Rollout und sollten vor der Implementierung in einer realen Umgebung sorgfältig geplant werden.
Voraussetzungen
Damit Du mit Deinem Deployment loslegen kannst, benötigst Du folgende Komponenten:
- PostgreSQL-Datenbank: Diese kann entweder in einer Azure-VM oder als verwalteter Dienst (z. B. Azure Database for PostgreSQL) bereitgestellt werden. PostgreSQL wird als empfohlene Datenbank für Keycloak verwendet
- Proxy: Ein Reverse Proxy wie nginx oder ein verwalteter Dienst wie der Azure Application Gateway. Dieser ist wichtig, um Anfragen an Keycloak weiterzuleiten und TLS-Verschlüsselung zu gewährleisten
Schritt für Schritt: Dein Keycloak Deployment
1. Keycloak mit öffentlicher IP starten
Beginnen wir mit einer einfachen Konfiguration: Wir deployen Keycloak mit einer öffentlichen IP und einer internen Datenbank (die sogenannte H2-Datenbank, die für Testzwecke gedacht ist). Alle Ressourcen liegen in einer bestehenden Resource Group.
Wir verwenden das offizielle Keycloak-Image von quay.io, da dieses direkt von Red Hat gepflegt wird. Der minimal notwendige Befehl sieht so aus:
az container create \
--resource-group rg-mykeycloak \
--name mykeycloak01 \
--image quay.io/keycloak/keycloak:latest \
--cpu 1 \
--memory 2 \
--ip-address Public \
--ports 8080 \
--dns-name-label mykeycloak01 \
--environment-variables KEYCLOAK_ADMIN=admin KEYCLOAK_ADMIN_PASSWORD=myInitialAccessPassword \
--command-line "opt/keycloak/bin/kc.sh, start-dev"
Erklärung
- –-dns-name-label: Damit wird ein DNS-Name für die öffentliche IP erzeugt. In Abhängigkeit von der Region ist Keycloak beispielsweise unter mykeycloak01.westeurope.azurecontainer.io erreichbar. Praktisch, wenn Du keine eigene Domain hast.
- CPU und Memory: Wir starten klein (1 CPU, 2 GB RAM). Benötigst Du mehr Ressourcen, kannst Du den Container einfach neu erstellen – ein Vorteil der Containerisierung
- KEYCLOAK_ADMIN und KEYCLOAK_ADMIN_PASSWORD: Diese Variablen legen den initialen Admin-Benutzer und dessen Passwort fest.
- Port 8080: Da wir zunächst ohne TLS arbeiten, bleibt der Standardport bestehen. Stelle sicher, dass der Port in etwaigen Network Security Groups (NSGs) freigeschaltet ist
Keycloak aufrufen
Wenn alles korrekt konfiguriert ist, erreichst Du Keycloak unter:
http://mykeycloak01.westeurope.azurecontainer.io:8080
2. Keycloak ins interne Subnet verschieben
Im nächsten Schritt entfernen wir die öffentliche IP und legen Keycloak in ein eigenes Subnet innerhalb eines Azure VNET. Das Subnet nennen wir sub-mykeycloak01:
az container create \
--resource-group rg-mykeycloak \
--name mykeycloak01 \
--image quay.io/keycloak/keycloak:latest \
--cpu 1 \
--memory 2 \
--vnet vnet-mykeycloak01 \
--subnet sub-mykeycloak01 \
--ports 8080 \
--environment-variables \
KEYCLOAK_ADMIN=admin
KEYCLOAK_ADMIN_PASSWORD=myInitialAccessPassword \
--command-line "opt/keycloak/bin/kc.sh, start-dev"
Zugriff testen:
Keycloak ist nun nur noch intern im VNET erreichbar. Ggf. kannst du den Zugriff testen, z.B. in dem du:
- Über eine VM im gleichen VNET
- Per Azure Bastion oder einem VPN-Gateway
Falls du an der Stelle keine Möglichkeit hast den Zugriff zu testen ist das aber auch kein Problem. So oder so solltest du Dir die Logs des Keycloak Containers anschauen und dort prüfen, ob dieser ordnungsgemäß gestartet wurde:
az container logs --resource-group rg-mykeycloak --name mykeycloak01
Möchtest Du die Logs live verfolgen, ersetze logs durch attach.
Private IP-Adresse herausfinden:
Die Ermittlung der IP-Adresse ist wichtig, da wir sie für die Konfiguration des Load Balancers benötigen:
az container show \
--resource-group rg-mykeycloak \
--name mykeycloak01 \
--query "{IPAddress:ipAddress.ip}" \
--output table
IPAddress
-----------
10.16.1.4
Reverse Proxy (nginx) konfigurieren
Wir verwenden einen nginx-Proxy, der Anfragen an Keycloak weiterleitet. Beispielkonfiguration:
server {
server_name mykeycloak01.germanywestcentral.cloudapp.azure.com;
location / {
proxy_pass http://10.16.1.4:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Host $host;
}
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/mykeycloak01.germanywestcentral.cloudapp.azure.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mykeycloak01.germanywestcentral.cloudapp.azure.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}
Übrigens: Wenn du so lange Hostnamen verwendest, stelle sicher das du die server_names_hash_bucket_size in nginx erhöst, beispielsweise auf 128.
Hier lauscht nginx auf Port 443 und leitet Anfragen an Keycloak weiter. Wichtig: Wir verwenden TLS mit einem Zertifikat von Let’s Encrypt. Das habe ich vorab mit Certbot bezogen, einem praktischen Tool, das automatisch TLS-Zertifikate von Let’s Encrypt erstellt und verwaltet.
Zusätzliche Parameter für Keycloak:
Keycloak muss nun wissen, dass Anfragen über einen Proxy kommen:
- KC_PROXY_HEADERS=xforwarded: Ermöglicht die Verarbeitung der X-Headers
- KC_PROXY_TRUSTED_ADDRESSES=98.67.205.42: IP des Proxys.
- KC_HOSTNAME: Setzt den Hostnamen (z. B. https://mykeycloak01.germanywestcentral.cloudapp.azure.com). Ohne diesen Parameter gibt es Probleme mit CORS (Cross-Origin Resource Sharing).
CORS sorgt dafür, dass Browser verhindern, dass eine Webseite auf Ressourcen einer anderen Domain zugreift, wenn diese nicht explizit dazu berechtigt wurde. Dies dient dem Schutz vor unerlaubtem Zugriff oder Datenmissbrauch. Ohne den Hostnamen könnte Keycloak keine Anfragen korrekt zuordnen, was dazu führen würde, dass viele Browser-Anfragen blockiert werden.
Neuer Befehl:
az container create \
--resource-group rg-mykeycloak \
--name mykeycloak01 \
--image quay.io/keycloak/keycloak:latest \
--cpu 1 \
--memory 2 \
--vnet vnet-mykeycloak01 \
--subnet sub-mykeycloak01 \
--ports 8080 \
--environment-variables
KEYCLOAK_ADMIN=admin \
KEYCLOAK_ADMIN_PASSWORD=myInitialAccessPassword \
KC_PROXY_HEADERS=xforwarded \
KC_PROXY_TRUSTED_ADDRESSES=98.67.205.42\
KC_HOSTNAME="https://mykeycloak01.germanywestcentral.cloudapp.azure.com" \
--command-line "/opt/keycloak/bin/kc.sh,start-dev"
Keycloak ist nun erreichbar unter:
https://mykeycloak01.germanywestcentral.cloudapp.azure.com/
PostgreSQL-Datenbank anbinden
Eine produktive Keycloak-Instanz sollte keine H2-Datenbank nutzen. Angenommen, Deine PostgreSQL-Datenbank ist eingerichtet: Die Details der Einrichtung würden den Rahmen hier sprengen, doch wichtig ist, dass eine entsprechende Datenbank angelegt wurde und ein Nutzer dafür konfiguriert ist:
CREATE DATABASE mykeycloak01db;
CREATE USER keycloak WITH PASSWORD 'mySillyPassword';
GRANT ALL PRIVILEGES ON DATABASE mykeycloak01db TO keycloak;
Außerdem musst Du sicherstellen, dass die pg_hba.conf und postgresql.conf so konfiguriert sind, dass der Zugriff von Keycloak auf die PostgreSQL-Datenbank möglich ist. Ebenso müssen Firewall-Regeln für Port 5432 gesetzt werden, damit die Verbindung reibungslos funktioniert
Verbindung herstellen:
Nun kannst du ganz einfach die Anbindung an die PostgreSQL-Datenbank über folgende Umgebungsvariablen konfigurieren:
KC_DB_URL="jdbc:postgresql://10.16.0.5:5432/mykeycloak01db" KC_DB_USERNAME=keycloak KC_DB_PASSWORD=mySillyPassword \
Passwort sicher speichern:
Das Passwort sollte nicht im Klartext im Befehl erscheinen, daher verwenden wir Azure Key Vault. Key Vault bietet eine sichere Möglichkeit, vertrauliche Informationen wie Passwörter zu speichern und zu verwalten. Wir legen also, falls noch nicht vorhanden, einen Key Vault an und erzeugen dort ein neues Secret, das als Datenbankpasswort für die PostgreSQL-Datenbank verwendet wird.

Aber wie reichen wir dies nun in den Azure-Create-Befehl hinein? Eine Möglichkeit ist, das Passwort über ein Skript aus Key Vault auszulesen und in eine Umgebungsvariable zu schreiben. Diese Variable kann dann im Azure-Create-Befehl referenziert werden. Ein Beispielskript könnte so aussehen:
#!/bin/bash
# Key Vault Name und Secret Name
VAULT_NAME="vault-mykeycloak01"
SECRET_NAME="kc-db-password"
# Passwort aus Key Vault abrufen
DB_PASSWORD=$(az keyvault secret show --name $SECRET_NAME --vault-name $VAULT_NAME --query value -o tsv)
Im Container-Befehl referenzieren wir dann diese Umgebungsvariable anstelle des Klartext-Passworts:
KC_DB_PASSWORD=$DB_PASSWORD
Abschließend nun nochmal das komplette Script:
#!/bin/bash
# Key Vault Name und Secret Name
VAULT_NAME="vault-mykeycloak01"
SECRET_NAME="kc-db-password"
# Passwort aus Key Vault abrufen
DB_PASSWORD=$(az keyvault secret show --name $SECRET_NAME --vault-name $VAULT_NAME --query value -o tsv)
# Container erstellen
az container create \
--resource-group rg-mykeycloak \
--name mykeycloak01 \
--image quay.io/keycloak/keycloak:latest \
--cpu 1 \
--memory 2 \
--vnet vnet-mykeycloak01 \
--subnet sub-mykeycloak01 \
--ports 8080 \
--environment-variables
KEYCLOAK_ADMIN=admin \
KEYCLOAK_ADMIN_PASSWORD=myInitialAccessPassword \
KC_PROXY_HEADERS=xforwarded \
KC_PROXY_TRUSTED_ADDRESSES=98.67.205.42 \
KC_HOSTNAME="https://mykeycloak01.germanywestcentral.cloudapp.azure.com" \
KC_DB_URL="jdbc:postgresql://10.16.0.5:5432/mykeycloak01db" \
KC_DB_USERNAME=keycloak \
KC_DB_PASSWORD=$DB_PASSWORD \
--command-line "/opt/keycloak/bin/kc.sh start"
Damit ist Deine Keycloak-Instanz produktionsnäher konfiguriert. Natürlich gibt es noch weitere Aspekte wie Skalierung, Backup oder Monitoring – aber für den Anfang hast Du eine solide Basis. Probier es aus und viel Erfolg 🙂
