Keycloak Migration und Setup mit Docker Compose
/ 6 min read
Table of Contents
Dieser Guide dokumentiert ein Szenario der Migration eines Keycloak Servers inklusive des Realms. Ausgangspunkt ist eine existierende Installation, Ziel ist es zu einer aktuellen Version auf einem neuen Server mit Docker Compose zu gelangen. Eine besondere Situation ist hierbei, dass Zertifikate einer lokalen CA eingebunden werden müssen – sowohl SSL-Zertifikate für die interne abgesicherte Kommunikation als auch das Zertifikat der Domäne.
Voraussetzungen
- Docker und Docker Compose (v2) installiert
- TLS-Zertifikat und Private Key, ausgestellt von einer internen CA
- Root-CA-Zertifikat (und ggf. Intermediate-Zertifikate) als PEM-Dateien
- Optional: eine Realm-Export-Datei (JSON) für den automatischen Import
Verzeichnisstruktur
Das Projektverzeichnis sollte wie folgt aufgebaut sein. Mit dieser Struktur funktioniert das weiter unten beschriebene Betreiben mit der Docker Compose Datei. Zu beachten ist hierbei, dass dies die Struktur außerhalb der Container ist. In der Compose-Datei können dann die internen Pfade und Mounts zugeordnet werden.
keycloak/├── docker-compose.yml├── .env├── keycloak-config/│ └── my-realm.json└── certs/ ├── tls/ │ ├── tls.crt │ └── tls.key └── ca/ ├── root-ca.pem └── intermediate-ca.pem # optionalDie Trennung von certs/tls/ (Keycloaks eigene Server-Identität) und certs/ca/ (vertrauenswürdige CA-Zertifikate für den Truststore) ist bewusst gewählt, um die beiden Funktionen sauber voneinander abzugrenzen und etwas Klarheit in den Dschungel zu bringen. Besonders nützlich, da es beim ersten Setup leicht den Überblick zu verlieren.
Konfiguration
.env
# === Postgres ===POSTGRES_DB=keycloakPOSTGRES_USER=keycloakPOSTGRES_PASSWORD=ein-sicheres-passwort
# === Keycloak Admin ===KC_BOOTSTRAP_ADMIN_USERNAME=adminKC_BOOTSTRAP_ADMIN_PASSWORD=ein-anderes-sicheres-passwort
# === Hostname (muss zum TLS-Zertifikat passen!) ===KC_HOSTNAME=keycloak.example.comDer KC_HOSTNAME muss exakt dem Common Name oder einem Subject Alternative Name des TLS-Zertifikats entsprechen. Andernfalls schlägt die Zertifikatsvalidierung fehl.
docker-compose.yml
services: postgres: image: postgres:17 healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] interval: 5s timeout: 5s retries: 5 volumes: - postgres_data:/var/lib/postgresql/data environment: POSTGRES_DB: ${POSTGRES_DB} POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
keycloak: image: quay.io/keycloak/keycloak:26.5.4 command: start --import-realm volumes: # Realm-Import - ./keycloak-config:/opt/keycloak/data/import:ro # TLS-Zertifikat + Key fuer Keycloak selbst - ./certs/tls:/opt/keycloak/conf/tls:ro # CA-Zertifikate fuer den Truststore - ./certs/ca:/opt/keycloak/conf/trustedca:ro environment: # --- Datenbank --- KC_DB: postgres KC_DB_URL: jdbc:postgresql://postgres:5432/${POSTGRES_DB} KC_DB_USERNAME: ${POSTGRES_USER} KC_DB_PASSWORD: ${POSTGRES_PASSWORD}
# --- Admin Bootstrap --- KC_BOOTSTRAP_ADMIN_USERNAME: ${KC_BOOTSTRAP_ADMIN_USERNAME} KC_BOOTSTRAP_ADMIN_PASSWORD: ${KC_BOOTSTRAP_ADMIN_PASSWORD}
# --- HTTPS --- KC_HTTPS_CERTIFICATE_FILE: /opt/keycloak/conf/tls/tls.crt KC_HTTPS_CERTIFICATE_KEY_FILE: /opt/keycloak/conf/tls/tls.key KC_HTTPS_PORT: "8443"
# --- HTTP (fuer Health-Checks oder Reverse Proxy) --- KC_HTTP_ENABLED: "true" KC_HTTP_PORT: "8080"
# --- Truststore --- KC_TRUSTSTORE_PATHS: /opt/keycloak/conf/trustedca
# --- Hostname --- KC_HOSTNAME: ${KC_HOSTNAME} KC_HOSTNAME_STRICT: "true"
# --- Proxy (falls hinter Reverse Proxy) --- # KC_PROXY_HEADERS: xforwarded
# --- Logging (bei Problemen hochdrehen) --- # KC_LOG_LEVEL: DEBUG ports: - "8080:8080" - "8443:8443" depends_on: postgres: condition: service_healthy
volumes: postgres_data:Hinweise zur Compose-Datei:
- Es wird
start(Production Mode) verwendet, nichtstart-dev. Nur im Production Mode greifen HTTPS- und Hostname-Einstellungen zuverlässig. - Die
KC_DB_URLreferenziert den Service-Namenpostgresdirekt, da diese zur Compose-Topologie gehört und nicht in die.envausgelagert werden sollte. - Der PostgreSQL-Port ist bewusst nicht nach außen exponiert. Keycloak erreicht die Datenbank über das interne Docker-Netzwerk.
--import-realmliest beim Start alle JSON-Dateien aus/opt/keycloak/data/import. Die Strategie istIGNORE_EXISTING– existierende Realms werden nicht überschrieben.
Dateiberechtigungen für Zertifikate
Der Keycloak-Container läuft als uid=1000 mit gid=0 (root). Das bedeutet: alle Dateien, die Keycloak lesen muss, brauchen entweder Owner root oder Gruppe root mit Leserecht. Dies war beim ersten Setup eine Fehlerquelle.
Ein häufiges Problem ist, dass der Private Key auf dem Host restriktive Berechtigungen hat (z.B. 640 root:andereradmin), die im Container nicht aufgelöst werden können.
# Korrekte Berechtigungen setzensudo chown root:root ./certs/tls/tls.keysudo chmod 640 ./certs/tls/tls.key
sudo chmod 644 ./certs/tls/tls.crtsudo chmod 755 ./certs/tls/sudo chmod 644 ./certs/ca/*.pemEntscheidend ist, dass die Gruppe root (GID 0) Leserechte auf den Key hat. Nur chown root:1000 zu setzen reicht nicht, weil die primäre Gruppe im Container GID 0 ist, nicht 1000.
Zur Überprüfung der tatsächlichen UID/GID im Container:
docker compose run --rm --entrypoint id keycloak# Erwartete Ausgabe: uid=1000(keycloak) gid=0(root) groups=0(root)Erster Start
cd /pfad/zum/keycloak-projekt
# Container startendocker compose up -d
# Logs beobachtendocker compose logs -f keycloakBeim ersten Start passiert Folgendes:
- PostgreSQL initialisiert die Datenbank
- Keycloak wartet auf den Postgres-Healthcheck
- Keycloak führt die Datenbank-Migration durch (Schemaerstellung)
- Der Realm-Import wird ausgeführt (Log:
Realm 'xyz' imported) - Der Bootstrap-Admin wird angelegt
- Keycloak startet und ist erreichbar
Das Web-Interface ist dann verfügbar unter https://<KC_HOSTNAME>:8443.
Als erstes sollte ein neuer Admin-Account angelegt und der Bootstrap-Admin aus der .env deaktiviert werden.
Troubleshooting
AccessDeniedException auf tls.key
ERROR: Failed to load 'https-trust-store' or 'https-key-' material: AccessDeniedException /opt/keycloak/conf/tls/tls.keyDie Dateiberechtigungen stimmen nicht. Siehe Abschnitt „Dateiberechtigungen für Zertifikate”. Die häufigste Ursache ist, dass die Dateigruppe nicht mit der Container-GID (0/root) übereinstimmt.
Webinterface nicht erreichbar
Prüfe zunächst den Hostnamen. Bei KC_HOSTNAME_STRICT: "true" antwortet Keycloak nur auf den exakten Hostnamen. Zum Testen kann temporär KC_HOSTNAME_STRICT: "false" gesetzt werden.
JGroups JOIN Timeouts
WARN: JOIN(...) sent to ... timed out (after 2000 ms)Das sind Warnungen von Infinispan/JDBC_PING, die nach früheren Cluster-Membern suchen. Bei einem Single-Node-Setup sind diese Meldungen harmlos. Nach 10 Versuchen wird Keycloak zum Singleton und läuft normal weiter.
Debugging aktivieren
Temporär in der docker-compose.yml:
KC_LOG_LEVEL: DEBUGDanach docker compose restart keycloak. Die Log-Ausgabe wird erheblich umfangreicher und zeigt die meisten Probleme im Detail.
Backup
Datenbank-Backup
docker compose exec postgres pg_dump \ -U ${POSTGRES_USER} ${POSTGRES_DB} \ > keycloak_backup_$(date +%Y%m%d).sqlDatenbank-Restore
docker compose exec -T postgres psql \ -U ${POSTGRES_USER} ${POSTGRES_DB} \ < keycloak_backup_YYYYMMDD.sqlRealm-Export über die Admin-CLI
Für einen aktuellen Realm-Export, der auch Secrets und Credentials enthält:
docker compose exec keycloak \ /opt/keycloak/bin/kc.sh export \ --dir /opt/keycloak/data/export \ --realm my-realm \ --users realm_fileUpdate auf eine neue Keycloak-Version
Die Datenbank-Migration erfolgt automatisch beim Start einer neuen Version. Keycloak erkennt Schemaänderungen und führt die nötigen Migrationsschritte durch.
Vorbereitung
- Upgrading Guide lesen: keycloak.org/docs/latest/upgrading – insbesondere die Breaking Changes für jede Zwischenversion prüfen
- Datenbank-Backup erstellen (siehe oben)
Durchführung
# 1. Image-Tag in docker-compose.yml anpassen# z.B. image: quay.io/keycloak/keycloak:26.5.4
# 2. Neues Image herunterladendocker compose pull
# 3. Container stoppendocker compose down
# 4. Mit neuem Image starten (DB-Migration läuft automatisch)docker compose up -d
# 5. Logs prüfendocker compose logs -f keycloakDie Reihenfolge ist wichtig: Erst die Compose-Datei ändern und das neue Image pullen, bevor die laufenden Container gestoppt werden. So wird die Downtime minimiert, weil der Image-Download bereits abgeschlossen ist, wenn die Container neu gestartet werden.
Rollback
Falls das Update fehlschlägt:
# Container stoppendocker compose down
# Image-Tag in docker-compose.yml auf alte Version zurücksetzen
# Datenbank-Backup wiederherstellen (falls Schema bereits migriert wurde)docker compose up -d postgresdocker compose exec -T postgres psql \ -U ${POSTGRES_USER} ${POSTGRES_DB} \ < keycloak_backup_YYYYMMDD.sql
# Keycloak mit alter Version startendocker compose up -dHinweise zur Absicherung für den Produktivbetrieb
- Den PostgreSQL-Port niemals ohne Firewall nach außen exponieren
- Starke, zufällig generierte Passwörter in der
.envverwenden - Die
.env-Datei mit restriktiven Berechtigungen versehen (chmod 600) - Einen Reverse Proxy (z.B. nginx, Traefik) vor Keycloak setzen und
KC_PROXY_HEADERS: xforwardedaktivieren - Regelmäßig Backups der PostgreSQL-Datenbank erstellen
- Das Keycloak-Image nicht auf
latestsetzen, sondern immer einen konkreten Versionstag verwenden