Zitadel auf einem VPS mit Docker selbst hosten
Zitadel empfiehlt 4 CPU-Kerne und 8 GB RAM. Richten Sie PostgreSQL, Masterkey, TLS, SMTP und Backups ein und prüfen Sie, was ein Upgrade mit der Datenbank macht.
Was Sie für das Self-Hosting von Zitadel auf einem VPS benötigen
Für das Self-Hosting von Zitadel auf einem VPS benötigen Sie einen Docker-Host, einen öffentlichen DNS-Namen, der auf diesen Host zeigt, PostgreSQL und ungefähr 4 CPU-Kerne mit 8 GB RAM. Zitadel ist ein Identity Provider. Es stellt Tokens über OIDC (OpenID Connect) und SAML (security assertion markup language) aus, damit Ihre anderen Dienste keine eigenen Benutzerlisten mehr verwalten müssen. Die Installation besteht aus curl und docker compose up. Ob der Betrieb dauerhaft zuverlässig funktioniert, hängt vom Masterkey, dem Datenbankbenutzer, SMTP (simple mail transfer protocol), dem Backup und dem ersten Upgrade ab.
Alle folgenden Schritte setzen Ubuntu 24.04, Docker Engine 24 oder neuer mit dem Compose-Plugin sowie einen Namen wie auth.example.com voraus, der bereits auf den Server aufgelöst wird.
Wie viel VPS benötigt Zitadel?
Der Compose-Schnellstart in der Zitadel-Dokumentation verlangt 2 GB RAM. Dieser Wert gilt für einen Laptop. Der Produktionsleitfaden von Zitadel nennt andere Werte.
The data behind this chart
[
{
"config": "Process floor, no load",
"cpu_cores": 0.5,
"ram_gb": 0.5
},
{
"config": "Single node, reduced setup",
"cpu_cores": 4,
"ram_gb": 8
},
{
"config": "HA node, logs and metrics on",
"cpu_cores": 4,
"ram_gb": 16
}
]Das sind veröffentlichte Empfehlungen und keine Messwerte eines laufenden Systems. Betrachten Sie sie als Beschreibung der Größenordnung. Der Zitadel-Prozess selbst ist klein und benötigt im Leerlauf etwa 0.5 GB RAM. Die CPU-Kerne werden für das Hashing von Passwörtern benötigt. Dieses ist absichtlich langsam, sodass eine Anmeldewelle zu einer CPU-Spitze führt. PostgreSQL ist der andere Teil des Ressourcenbedarfs: Derselbe Leitfaden kalkuliert etwa einen Kern pro 100 Anfragen pro Sekunde und 4 GB RAM pro Kern. Zusammengenommen ergeben sich die im Leitfaden genannten 4 Kerne und 8 GB RAM für einen einzelnen Knoten oder 16 GB pro Knoten, sobald Logging und Metriken aktiviert sind.
Ein VPS mit 2 GB startet diesen Stack also, liegt aber unter der vom Projekt empfohlenen Größe für einen ernsthaften Einsatz. Der Login-Dienst ist die Abhängigkeit aller anderen Dienste. Wenn er nicht verfügbar ist, lässt kein Dienst, der ihm vertraut, Benutzer hinein. Es ist eine vernünftige Entscheidung, dass Ihnen 8 GB für die Authentifizierung zu teuer sind. Diese Entscheidung jetzt zu treffen ist außerdem deutlich günstiger als nach einer Migration. Der Vergleich von Keycloak, Authentik und Zitadel zeigt, wie viel Speicher und Betriebsaufwand die einzelnen Lösungen benötigen. Ein selbst gehosteter Authentik-Server ist auf einem kleineren System meist die übliche Antwort.
Stack abrufen und Version festlegen
mkdir zitadel-compose && cd zitadel-compose
curl -fsSLO https://raw.githubusercontent.com/zitadel/zitadel/main/deploy/compose/docker-compose.yml
curl -fsSLO https://raw.githubusercontent.com/zitadel/zitadel/main/deploy/compose/.env.example
cp .env.example .env
chmod 600 .envDiese Datei definiert vier Dienste, die Sie tatsächlich ausführen werden. Traefik ist der Reverse Proxy: Er leitet Anfragen anhand des Pfads weiter und beendet mit dem weiter unten beschriebenen Overlay TLS (Transport Layer Security). zitadel-api ist die Go-Binärdatei auf Port 8080. zitadel-login ist die unter /ui/v2/login bereitgestellte Anmeldeschnittstelle. postgres enthält alle Komponenten. Ein Redis-Cache und ein OpenTelemetry-Collector befinden sich in derselben Datei hinter Compose-Profilen und bleiben deaktiviert, bis Sie sie aktivieren.
Führen Sie docker compose up jetzt noch nicht aus. Beim ersten Start wird die Instanz erstellt. Mehrere der folgenden Einstellungen können danach nur mit zusätzlichem Aufwand geändert werden.
Der von Ihnen kopierte .env legt seine eigenen Image-Tags fest:
ZITADEL_VERSION=v4.16.0
TRAEFIK_IMAGE=traefik:v3.7.7
POSTGRES_IMAGE=postgres:17.10-alpineDie aktuelle v4-Version ist v4.17.1. Sie wurde am 14 August 2026 veröffentlicht. Setzen Sie ZITADEL_VERSION auf die Version, die Sie ausführen möchten, und bleiben Sie auf der v4-Linie, statt immer die neueste Version zu verwenden. Das obige curl ruft docker-compose.yml aus dem main-Branch ab. Dieser ist an keine Version gebunden. Übertragen Sie daher Kopien beider Dateien in ein Git-Repository. Andernfalls liefert derselbe Befehl auf einem neuen System im nächsten Monat eine andere Datei, und Sie wissen nicht, was sich geändert hat.
Postgres einen eigenen Benutzer und ein echtes Passwort geben
Die mitgelieferte .env verbindet Zitadel als Superuser mit PostgreSQL. Das Passwort lautet postgres:
POSTGRES_ADMIN_USER=postgres
POSTGRES_ADMIN_PASSWORD=postgres
ZITADEL_DATABASE_POSTGRES_DSN=postgresql://postgres:postgres@postgres:5432/zitadel?sslmode=disableBei der Absicherung gibt es hier eine Falle. Die Zitadel-Dokumentation weist an, POSTGRES_ZITADEL_PASSWORD an .env anzuhängen. Die zugrunde liegende docker-compose.yml liest diese Variable jedoch nie ein. Das Setzen der Variable hat daher keine Wirkung. Wenn Sie stattdessen nur POSTGRES_ADMIN_PASSWORD ändern, schlägt die Verbindung fehl, weil das Passwort zusätzlich direkt im DSN (Data Source Name) eingetragen ist. Der DSN legt fest, wie Zitadel die Verbindung herstellt.
Die Kommentare in .env.example erklären den Rest eindeutig: Wenn ein DSN konfiguriert ist, verwendet Zitadel diesen Benutzer direkt und erstellt keinen unprivilegierten Benutzer. Die Rolle muss daher vor dem ersten Start vorhanden sein. Generieren Sie ein Passwort, starten Sie Postgres allein und erstellen Sie die Rolle.
tr -dc A-Za-z0-9 </dev/urandom | head -c 32; echo
docker compose --env-file .env -f docker-compose.yml up -d postgres
docker compose --env-file .env -f docker-compose.yml exec -T postgres \
psql -U postgres -d postgres <<'SQL'
CREATE ROLE zitadel LOGIN PASSWORD 'the-password-you-generated';
ALTER DATABASE zitadel OWNER TO zitadel;
SQL
docker compose --env-file .env -f docker-compose.yml exec -T postgres \
psql -U postgres -d zitadel -c 'ALTER SCHEMA public OWNER TO zitadel;'Diese psql-Aufrufe werden im Container über dessen lokalen Socket ausgeführt. Dem Socket vertraut das offizielle Postgres-Image, daher wird kein Passwort abgefragt. Entscheidend ist der Besitz. Bei PostgreSQL 15 und neuer kann ein einfaches GRANT ALL PRIVILEGES ON DATABASE keine Tabellen mehr im Schema public erstellen. Die Einrichtungsphase von Zitadel schlägt dann beim Anlegen der Schemas mit einem Berechtigungsfehler fehl. Wenn die Rolle Eigentümer der Datenbank und des Schemas ist, tritt dieses Problem nicht auf.
Verweisen Sie den DSN jetzt auf die neue Rolle, und setzen Sie in derselben Datei ein echtes Admin-Passwort:
POSTGRES_ADMIN_PASSWORD=a-32-character-random-string
ZITADEL_DATABASE_POSTGRES_DSN=postgresql://zitadel:the-password-you-generated@postgres:5432/zitadel?sslmode=disablesslmode=disable ist hier ausreichend, weil Postgres nur im privaten Compose-Netzwerk erreichbar ist und sein Port nie auf dem Host veröffentlicht wird. Prüfen Sie nach dem ersten vollständigen Start, ob die Rolle tatsächlich Eigentümer ihrer Daten ist:
docker compose exec -T postgres psql -U zitadel -d zitadel -c '\dn'Die Ausgabe sollte ein Schema eventstore und ein Schema projections enthalten. Eine leere Liste bedeutet, dass die Einrichtungsphase nicht bis zu diesem Punkt gelangt ist. Im Log des API-Containers steht der Grund.
Der Masterkey und was sein Verlust kostet
Zitadel verschlüsselt Secrets, bevor sie gespeichert werden: Client-Secrets, Zugangsdaten für Identity Provider, das SMTP-Passwort, OTP-Seeds und Maschinenschlüssel. Der Masterkey entsperrt all diese Daten. Er ist genau 32 Zeichen lang. Die Dokumentation weist ausdrücklich auf die Konsequenz hin: Er kann nicht geändert werden, ohne den Zugriff auf verschlüsselte Daten zu verlieren.
Generieren Sie einen Masterkey und ersetzen Sie damit die Platzhalterzeile in .env:
tr -dc A-Za-z0-9 </dev/urandom | head -c 32; echoBearbeiten Sie die Zeile ZITADEL_MASTERKEY=MasterkeyNeedsToHave32Characters, statt eine zweite Zeile anzuhängen. Compose verwendet bei einem wiederholten Schlüssel die letzte Definition. Das Anhängen funktioniert daher zwar, aber eine Datei mit zwei Masterkey-Zeilen ist eine Fehlerquelle für die nächste Person, die sie liest.
Überlegen Sie nun, wo dieser Schlüssel gespeichert wird. Die Compose-Datei startet den API-Container wie folgt:
command: start-from-init --masterkey "${ZITADEL_MASTERKEY}"Der Masterkey steht damit in der Kommandozeile des Containers. docker inspect zeigt ihn allen Personen, die Zugriff auf den Docker-Socket haben. Auf einem VPS mit nur einem Administrator ist das ein vertretbarer Kompromiss. Der Modus in .env schützt den Schlüssel auf der Festplatte. Wenn das nicht akzeptabel ist, binden Sie den Schlüssel als Datei ein und verwenden Sie stattdessen --masterkeyFile /run/secrets/zitadel-masterkey. Dadurch bleibt der Wert außerhalb der Prozessargumente.
Kopieren Sie den Masterkey vor dem ersten Start in Ihren Passwortmanager. Er ist nicht in einem Datenbankdump enthalten. Ein Dump, der unter einem anderen Masterkey wiederhergestellt wird, erzeugt daher eine Instanz, die ihre eigenen Secrets nicht lesen kann. Bewahren Sie den Schlüssel an einem anderen Ort als dem Archiv mit dem Dump auf. So enthält ein gestohlenes Backup nicht zugleich die verschlüsselten Daten und den dazugehörigen Schlüssel.
Legen Sie die externe Domain vor dem ersten Start fest
ZITADEL_DOMAIN in .env speist ZITADEL_EXTERNALDOMAIN im Container. Dabei handelt es sich um den Namen, den Ihre Benutzer eingeben. Zitadel leitet daraus den OIDC-Issuer, die Basis-URI der Anmeldeschnittstelle, die SAML-Endpunkte und den Anmeldenamen des ersten Administrators ab. Der Wert ist daher nicht rein kosmetisch.
ZITADEL_DOMAIN=auth.example.com
ZITADEL_EXTERNALPORT=443
ZITADEL_EXTERNALSECURE=trueZitadel ermittelt anhand des Headers Host, mit welcher Instanz Sie kommunizieren. Wenn dieser Header keiner bekannten Domain entspricht, antwortet jede Anfrage auf dieselbe Weise:
ID=QUERY-1kIjX Message=Instance not foundDas ist der häufigste Fehler bei selbst gehostetem Zitadel. Fast immer liegt eine von zwei Ursachen vor. Entweder ist ZITADEL_DOMAIN nicht der Name, den Sie im Browser aufrufen, oder ein vorgeschalteter Proxy schreibt Host in die Upstream-Adresse um. Der Fehler tritt ebenfalls auf, wenn Sie die IP-Adresse des Servers statt des Namens aufrufen.
Sie können diese Werte später ändern. Zitadel muss seine Einrichtungsphase erneut ausführen, damit die Änderung übernommen wird. Jede bereits registrierte Anwendung behält außerdem ihre alten Redirect-URIs. Den endgültigen Namen jetzt festzulegen ist wesentlich einfacher, als ihn später zu ändern.
TLS-Terminierung mit dem Let's-Encrypt-Overlay
Für eine öffentliche Domain fügen Sie das Let's-Encrypt-Overlay von Zitadel hinzu. Es stellt Traefik auf die ACME-HTTP-Challenge (automatische Zertifikatsverwaltung) um und ersetzt die veröffentlichten Ports durch 80 und 443. Daher darf kein anderer Dienst auf dem Server einen dieser Ports belegen.
curl -fsSLO https://raw.githubusercontent.com/zitadel/zitadel/main/deploy/compose/docker-compose.mode-letsencrypt.yml
echo 'LETSENCRYPT_EMAIL=ops@example.com' >> .envDas Overlay setzt außerdem ZITADEL_EXTERNALPORT: 443 und ZITADEL_EXTERNALSECURE: true im API-Container. Dadurch stimmen die öffentliche URL und die von Zitadel selbst erzeugten URLs überein. Der A-Record muss bereits vor dem Start aufgelöst werden können, weil die HTTP-Challenge sonst fehlschlägt.
Wenn Sie TLS bereits mit nginx oder auf einem Load Balancer terminieren, verwenden Sie stattdessen docker-compose.mode-external-tls.yml und setzen TRAEFIK_TRUSTED_IPS auf die Bereiche, von denen Ihr Proxy Anfragen sendet. Traefik berücksichtigt X-Forwarded-*-Header nur von Adressen aus dieser Liste. Ein falscher Wert führt daher dazu, dass das weitergeleitete Protokoll verworfen wird und Zitadel für eine HTTPS-Site http://-URLs erzeugt.
Ein vorgeschalteter Proxy muss zwei Anforderungen erfüllen, auf die Zitadel strikt besteht. Er muss HTTP/2 zum Backend verwenden, weil die API gRPC nutzt. Außerdem muss er Host zusammen mit X-Forwarded-Proto: https unverändert weiterleiten. Das nginx-Beispiel von Zitadel zeigt den grundsätzlichen Aufbau:
server {
listen 443 ssl;
http2 on;
ssl_certificate /etc/certs/selfsigned.crt;
ssl_certificate_key /etc/certs/selfsigned.key;
location /ui/v2/login {
proxy_pass http://login-external-tls:3000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
}
location / {
grpc_pass grpc://zitadel-external-tls:8080;
grpc_set_header Host $host;
grpc_set_header X-Forwarded-Proto https;
}
}Die dort verwendeten Upstream-Namen sind die Container aus der Testumgebung von Zitadel. Ersetzen Sie sie daher durch Ihre eigenen. Wenn Sie Zitadel über einen anderen Port als 443 bereitstellen, verwenden Sie grpc_set_header Host $host:$server_port;, damit der Port mit dem Header übertragen wird. Der übrige Teil ist ein gewöhnlicher virtueller Host. Eine nginx-Reverse-Proxy-Konfiguration Zeile für Zeile lesen behandelt die nicht für Zitadel spezifischen Bestandteile.
Der erste Administrator und das Erzwingen der Passwortänderung
Beim ersten Start werden eine Instanz, eine Organisation und ein menschlicher Administrator erstellt. Der Anmeldename setzt sich aus zitadel-admin@, zitadel. und Ihrer externen Domain zusammen. Mit ZITADEL_DOMAIN=auth.example.com lautet er:
zitadel-admin@zitadel.auth.example.comDas Passwort lautet Password1!, sofern Sie kein eigenes festlegen. Der Upstream-Standard von Zitadel erzwingt beim ersten Anmelden eine Änderung. Die mitgelieferte Compose-Datei überschreibt diesen Standard:
ZITADEL_FIRSTINSTANCE_ORG_HUMAN_PASSWORDCHANGEREQUIRED: falseDiese Zeile ist in docker-compose.yml fest codiert und wird nicht aus .env gelesen. Legen Sie Ihre eigenen Werte daher in einem kleinen eigenen Overlay ab. Nennen Sie es docker-compose.local.yml:
services:
zitadel-api:
environment:
ZITADEL_FIRSTINSTANCE_ORG_HUMAN_EMAIL_ADDRESS: you@example.com
ZITADEL_FIRSTINSTANCE_ORG_HUMAN_PASSWORD: "a-long-temporary-password"
ZITADEL_FIRSTINSTANCE_ORG_HUMAN_PASSWORDCHANGEREQUIRED: "true"Compose lädt docker-compose.override.yml automatisch nur dann, wenn Sie es ohne das Flag -f ausführen. Jeder Befehl in der Zitadel-Anleitung übergibt jedoch -f, wodurch dieses Verhalten deaktiviert wird. Statt eine wachsende Liste von Flags zu wiederholen, legen Sie die Dateiliste in .env fest:
COMPOSE_FILE=docker-compose.yml:docker-compose.mode-letsencrypt.yml:docker-compose.local.ymlStarten Sie Zitadel nun:
docker compose pull
docker compose up -d --wait--wait wartet, bis die Healthchecks erfolgreich sind. Wenn der API-Container diesen Zustand nicht erreicht, beendet Compose den Vorgang mit dependency failed to start: container zitadel-compose-zitadel-api-1 is unhealthy. docker compose logs zitadel-api enthält die Ursache. Beim ersten Start liegt sie meistens an der Länge des Masterkeys oder an der Datenbank-DSN.
Melden Sie sich unter https://auth.example.com/ui/console an, ändern Sie das Passwort und aktivieren Sie für dieses Konto einen zweiten Faktor, bevor Sie weitere Objekte erstellen. Jeder ZITADEL_FIRSTINSTANCE_*-Wert gilt nur während der Erstellung der ersten Instanz. Sobald die Instanz existiert, haben Änderungen daran keinerlei Wirkung mehr.
Warum der Passwort-Reset erst funktioniert, wenn SMTP eingerichtet ist
Ein Identity Provider, der keine E-Mails senden kann, ist auf eine Weise defekt, die wochenlang unbemerkt bleiben kann. Zitadel sendet E-Mails für Benutzereinladungen, die Bestätigung von E-Mail-Adressen, Passwort-Reset-Links, Einmalcodes und Hinweise zur Inanspruchnahme von Domains. Wenn kein SMTP-Provider konfiguriert ist, meldet die Console die Aktion weiterhin als abgeschlossen. Die Nachricht wird dann an einen Benachrichtigungs-Worker übergeben, der sie nicht versenden kann. Die Standardwerte geben diesem Worker MaxAttempts: 3 und MaxTtl: 5m. Er versucht den Versand daher einige Male über wenige Minuten hinweg und beendet sich anschließend. Die Person, die auf den Link wartet, erhält keine Information.
Konfigurieren Sie dies in der Console unter den Instanzeinstellungen bei https://auth.example.com/ui/console/settings. Das Formular für den SMTP-Provider fragt nach einer Absender-E-Mail-Adresse, einem Absendernamen, Host und Port, einem Benutzer, einem SMTP-Passwort und einer TLS-Option. Verwenden Sie vor dem Speichern die Testschaltfläche in diesem Formular, da sie eine echte Nachricht sendet: Sie kommt entweder an oder nicht.
Dafür gibt es einen passenden Satz von Umgebungsvariablen: ZITADEL_DEFAULTINSTANCE_SMTPCONFIGURATION_SMTP_HOST und die zugehörigen Variablen. Sie werden angewendet, wenn eine Instanz erstellt wird. Bei einem Stack, der bereits läuft, haben sie keine Wirkung. Für eine vorhandene Instanz ist daher die Console der richtige Ort.
Bei der Zustellung von einem VPS sind zwei Punkte wichtig, da der Versand meistens daran scheitert. Die meisten Provider blockieren bei neuen Konten den ausgehenden Port 25. Ein direkter Versand an den Mailserver des Empfängers läuft dann mit einer wenig hilfreichen Fehlermeldung in einen Timeout. Verwenden Sie stattdessen ein authentifiziertes Relay über Port 587. Veröffentlichen Sie außerdem SPF- (Sender Policy Framework) und DKIM-Records (DomainKeys Identified Mail) für die sendende Domain. Andernfalls landet der Reset-Link im Spam-Ordner. Für den Benutzer sieht das genau so aus, als wäre die E-Mail nie versendet worden.
Testen Sie den Versand, bevor Sie jemanden einladen. Erstellen Sie einen temporären Benutzer, fordern Sie einen Passwort-Reset an und prüfen Sie, ob die Nachricht ankommt. Falls dies nicht geschieht, nennt docker compose logs -f zitadel-api den SMTP-Fehler. Das SMTP-Passwort wird verschlüsselt in der Datenbank gespeichert. Auch diesen Wert schützt der masterkey für Sie.
PostgreSQL und den Masterkey getrennt sichern
Alle Daten, die Zitadel kennt, liegen in PostgreSQL. Der Masterkey entschlüsselt diese Daten. Sichern Sie beides an zwei unterschiedlichen Orten.
Erstellen Sie zuerst den Dump:
sudo install -d -m 700 /srv/zitadel-backups
docker compose exec -T postgres \
pg_dump -U postgres -Fc zitadel > "/srv/zitadel-backups/zitadel-$(date +%F).dump"-Fc ist das benutzerdefinierte Format. Es komprimiert die Daten beim Export, und pg_restore kann daraus gezielt einzelne Inhalte lesen. exec -T deaktiviert das Terminal. Das ist wichtig, weil der Befehl über cron ohne verbundenes Terminal ausgeführt wird.
Übertragen Sie dieses Verzeichnis anschließend mit restic an einen externen Standort. restic verschlüsselt und dedupliziert die Daten:
export RESTIC_REPOSITORY="sftp:backup@backup.example.com:/srv/restic/zitadel"
export RESTIC_PASSWORD_FILE=/root/.restic-password
restic init
restic backup /srv/zitadel-backups
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prunerestic init wird nur einmal am ersten Tag ausgeführt. Legen Sie den Dump und die beiden letzten Befehle in /usr/local/bin/zitadel-backup.sh ab und führen Sie das Skript jede Nacht aus:
0 3 * * * /usr/local/bin/zitadel-backup.shSichern Sie .env und alle verwendeten Compose-Dateien in git. Der Masterkey ist die einzige Ausnahme. Er gehört in Ihren Passwortmanager und an einen zweiten Speicherort, der nicht dieses restic-Repository ist. Ein Archiv, das Datenbank und Entschlüsselungsschlüssel gemeinsam enthält, ist keine Sicherung eines verschlüsselten Systems mehr.
Ein Backup, das Sie noch nicht wiederhergestellt haben, ist nur eine Vermutung. Stellen Sie es in einer temporären Datenbank auf demselben Server wieder her und prüfen Sie den Inhalt:
docker compose exec -T postgres createdb -U postgres zitadel_restore_test
docker compose exec -T postgres pg_restore -U postgres -d zitadel_restore_test \
< /srv/zitadel-backups/zitadel-2026-08-21.dump
docker compose exec -T postgres psql -U postgres -d zitadel_restore_test -c '\dt eventstore.*'
docker compose exec -T postgres dropdb -U postgres zitadel_restore_testEine Liste von Tabellen im Schema eventstore bedeutet, dass der Dump gültig ist. Meldet der Befehl, dass das Schema nicht existiert, ist der Dump nicht gültig. Sie haben das an einem Tag festgestellt, an dem der Fehler nichts kostet. Das allgemeine Muster zum Sichern und Aktualisieren eines Compose-Stacks gilt hier nahezu unverändert. Den Masterkey aus demselben Archiv herauszuhalten, ist der einzige Zitadel-spezifische Teil.
Zitadel aktualisieren, ohne die Instanz zu verlieren
Ein Upgrade ist eine Versionsänderung in .env, gefolgt von zwei Befehlen:
docker compose pull
docker compose up -d --waitVerstehen Sie zuerst, was der zweite Befehl tut, bevor Sie ihn für eine Instanz ausführen, bei der sich Benutzer anmelden. Der Befehl des Containers lautet start-from-init. Er führt die Init- und Setup-Phasen aus, bevor der Dienst Anfragen verarbeitet. Die Setup-Phase enthält die Datenbankmigrationen. Eine Versionsänderung führt daher beim Start des Containers unbeaufsichtigt Schema-Migrationen für Ihre aktive Datenbank aus, während --wait auf einen erfolgreichen Healthcheck wartet. Genau deshalb ist der oben beschriebene Wiederherstellungstest nicht optional.
Erstellen Sie unmittelbar vor dem Upgrade einen neuen Dump. Der Dump der vergangenen Nacht ist dafür nicht ausreichend.
Überspringen Sie keine Hauptversion. Für den Wechsel von v3 zu v4 müssen Sie zuerst v3.4.1 oder höher verwenden, weil v4 die alten OIDC-Signaturschlüssel entfernt hat. Dadurch können Tokens, die mit den alten Schlüsseln signiert wurden, ab dem Versionswechsel nicht mehr verifiziert werden. Zitadels technische Empfehlung A-10017 beschreibt dieses Verhalten. Die Lösung besteht darin, die neuere v3-Version lange genug auszuführen, bis alte Tokens abgelaufen sind, und erst danach das Upgrade durchzuführen.
Überwachen Sie die Setup-Phase mit docker compose logs -f zitadel-api. Migrationen können bei einem großen Eventstore mehrere Minuten dauern. Traefik leitet erst dann Anfragen an die API weiter, wenn der Healthcheck erfolgreich ist. Die Website ist während dieses Zeitraums daher nicht verfügbar. Planen Sie diese Unterbrechung ein, statt sie erst während des Upgrades festzustellen.
Ein Rollback besteht nicht einfach darin, wieder den alten Tag zu setzen. Nach der Ausführung der Migrationen versteht die ältere Binärdatei das vorgefundene Schema nicht mehr. Für ein Rollback müssen Sie daher den Dump wiederherstellen. Sobald die Instanz echte Benutzer enthält, wechseln Sie zu docker-compose.prodlike.yml. Dieses Overlay führt Init und Setup als separate Schritte vor dem Start aus. Dadurch lösen Sie eine Migration gezielt aus und überwachen sie, statt sie als Nebeneffekt eines Containerneustarts auszuführen.
Worauf Ihr neuer Identity Provider verweisen muss
Erstellen Sie in der Console ein Projekt und anschließend eine Anwendung darin. Wählen Sie für moderne Anwendungen OIDC. Zitadel stellt Ihnen dann eine Client-ID, ein Client-Secret und ein Discovery-Dokument unter https://auth.example.com/.well-known/openid-configuration bereit. Die meisten selbst gehosteten Anwendungen mit Single-Sign-on-Unterstützung erwarten genau diese Angaben.
Viele Anwendungen unterstützen Single Sign-on nicht oder nur in einem kostenpflichtigen Tarif. Im ersten Fall macht oauth2-proxy vor der Anwendung aus jedem HTTP-Dienst einen Dienst, den Zitadel absichern kann. Im zweiten Fall sollten Sie die SSO-Gebühr bei selbst gehosteten Anwendungen lesen, bevor Sie eine Migration auf Grundlage einer Funktion planen, für die Sie noch nicht bezahlt haben.
FAQ
Wie viel RAM und CPU benötigt ein selbst gehostetes Zitadel?
Der Produktionsleitfaden von Zitadel empfiehlt für einen einzelnen Knoten mit reduzierter Konfiguration etwa 4 CPU-Kerne und 8 GB RAM sowie 16 GB RAM pro Knoten, wenn Logging und Metriken aktiviert sind. PostgreSQL wird separat dimensioniert: ungefähr ein Kern pro 100 Anfragen pro Sekunde und 4 GB RAM pro Kern. Der Compose-Schnellstart läuft mit weniger als 2 GB RAM. Das reicht zum Ausprobieren und liegt unter der vom Projekt empfohlenen Größe für ein System, von dem andere Dienste abhängen.
Was passiert, wenn ich den Zitadel-Masterkey verliere?
Alles, was damit verschlüsselt wurde, bleibt verschlüsselt. Client-Secrets, Zugangsdaten der Identity Provider, das SMTP-Passwort und die Seeds für Einmalpasswörter können nicht entschlüsselt werden. Der Schlüssel kann nachträglich nicht geändert werden. Ein Datenbank-Dump allein stellt keine funktionsfähige Instanz wieder her, weil der Dump die Chiffretexte, aber keinen Schlüssel enthält. Speichern Sie den Masterkey in einem Passwortmanager und an einem Ort, der vom Backup mit dem Dump getrennt ist. Wenn beides verloren ist, bleibt nur der vollständige Neuaufbau der Instanz.
Warum kommen E-Mails zum Zurücksetzen von Zitadel-Passwörtern nie an?
Weil kein SMTP-Provider konfiguriert ist oder der konfigurierte Provider die Nachricht nicht zustellen kann. Zitadel stellt jede Benachrichtigung standardmäßig mit drei Versuchen in eine Worker-Warteschlange und meldet in der Console unabhängig davon einen Erfolg. Der Fehler bleibt daher unbemerkt. Konfigurieren Sie den SMTP-Provider in den Instanzeinstellungen und verwenden Sie dort die Testschaltfläche. Sie sendet eine echte Nachricht. Verwenden Sie von einem VPS aus ein authentifiziertes Relay auf Port 587, da die meisten Provider ausgehende Verbindungen zu Port 25 blockieren. Veröffentlichen Sie außerdem SPF- und DKIM-Einträge für die Absenderdomain, damit die Nachricht nicht als Spam herausgefiltert wird.
Kann ich die externe Domain von Zitadel nach der Installation ändern?
Ja, aber nicht durch eine alleinige Änderung von .env. Ändern Sie ZITADEL_EXTERNALDOMAIN, ZITADEL_EXTERNALPORT und ZITADEL_EXTERNALSECURE. Lassen Sie Zitadel anschließend die Setup-Phase erneut ausführen, damit die Änderung übernommen wird. Bereits registrierte Anwendungen behalten ihre alten Redirect-URIs und müssen manuell aktualisiert werden. Jede Anfrage, deren Host-Header nicht zu einer Zitadel bekannten Domain passt, erhält Instance not found zurück. Wenn Sie den endgültigen Namen vor dem ersten Start festlegen, vermeiden Sie diese Probleme.