Zum Hauptinhalt springen
S-EDV news
← Alle Anleitungen
📘 Anleitung Server & Netzwerk 19.07.2026 · 9 min Lesezeit

Zulip mit Docker installieren: Strukturierter Team-Chat mit Kanälen und Themen

Zulip 12.3 auf dem offiziellen docker-zulip-Weg betreiben: Release-Tag klonen, compose.override.yaml, Docker Secrets aus der .env, lokale Ports mit !override, Reverse Proxy, getestetes Backup mit app:restore und Update.

Geprüft am 14.10.2026 · für docker-zulip 12.3-0

WerbelinksMit * markierte Links sind Werbelinks: Bei einem Kauf erhalten wir eine Provision, der Preis bleibt gleich. Als Amazon-Partner verdiene ich an qualifizierten Verkäufen. Mehr dazu

Leere farbige Sprechblasen aus Papier, in Gruppen an Schnüren über einem Schreibtisch mit kompaktem Mini-Server und Kaffeetasse

Zulip ist ein Team-Chat mit einem eigenen Ordnungsprinzip: Nachrichten gehören zu einem Kanal und innerhalb des Kanals zu einem Thema (Topic). So lassen sich parallele Diskussionen auch Stunden später noch nachlesen. Diese Anleitung zeigt den offiziellen Docker-Weg des Projekts mit dem Repository docker-zulip und dem Image ghcr.io/zulip/zulip-server:12.3-0: Konfiguration in compose.override.yaml, Geheimnisse als Docker Secrets aus einer .env, Betrieb hinter einem Reverse Proxy, Backup und Update per Git-Tag.

Voraussetzungen

  • Server: x86_64 oder ARM64 (aarch64). Laut Zulip-Doku reichen für kleinere Organisationen 1 CPU und 2 GB RAM mit 2 GB Swap, ab 100 Nutzern 2 CPUs und 4 GB RAM. Unter 5 GB RAM verlangt Zulip Swap. Im Test auf einem Host mit 8 GB RAM belegte der Zulip-Container im Leerlauf rund 3 GB, die übrigen Dienste zusammen etwa 330 MB. Planen Sie 4 GB RAM als Untergrenze ein.
  • Speicher: mindestens 10 GB frei für eine Organisation mit einigen Dutzend Nutzern, SSD empfohlen.
  • Software: Linux mit Docker Engine, Compose-Plugin und git.
  • Netz: ein DNS-Name wie chat.example.com. Zulip akzeptiert keine IP-Adresse als EXTERNAL_HOST. Zulip verlangt HTTPS, entweder über einen Reverse Proxy oder über das eingebaute Certbot.
  • Mail: Zugangsdaten für einen SMTP-Server. Ohne Mailversand funktionieren Einladungen und Passwort-Zurücksetzen nicht.
DienstImageAufgabe
zulipghcr.io/zulip/zulip-server:12.3-0Anwendung mit nginx, Volume zulip unter /data
databasezulip/zulip-postgresql:14PostgreSQL 14 mit Zulip-Erweiterungen, Volume postgresql-14
memcachedmemcached:alpineCache mit SASL-Passwort
rabbitmqrabbitmq:4.2Warteschlangen, Volume rabbitmq
redisredis:alpineRate-Limits und Sitzungsdaten, mit Passwort

Wichtig für Bestandsinstallationen: Das frühere Image zulip/docker-zulip auf Docker Hub ist mit dem Ende der 11.x-Reihe eingestellt. Ab 12.x liegt das Image auf GHCR, die Konfiguration gehört in compose.override.yaml, Umgebungsvariablen des alten Images lehnt der Container seit 12.1 beim Start ab.

Schritt 1: Repository auf den Release-Tag klonen

Der Git-Tag des Repositorys ist laut Doku die maßgebliche Version: Er hält compose.yaml, Beispieldatei und Image-Tag zusammen. Das Projekt veröffentlicht keine gleitenden Tags wie latest.

sudo git clone https://github.com/zulip/docker-zulip.git /opt/docker-zulip
cd /opt/docker-zulip
sudo git checkout -B release 12.3-0
sudo cp compose.override.yaml.example compose.override.yaml

Die compose.yaml bearbeiten Sie nicht. Alle eigenen Einstellungen landen in der compose.override.yaml, die Git ignoriert. So bleibt ein späteres Update konfliktfrei.

Verifizieren: git describe --tags zeigt 12.3-0, grep image: compose.yaml enthält ghcr.io/zulip/zulip-server:12.3-0.

Schritt 2: Geheimnisse in der .env hinterlegen

Die Dienste authentifizieren sich untereinander mit Docker Secrets. Die Beispieldatei liest sie aus Umgebungsvariablen der Compose-Umgebung, also aus einer .env im Projektordner. Diese Variablen gelangen nicht direkt in die Container, sondern nur über den secrets-Mechanismus.

cd /opt/docker-zulip
{
  for s in POSTGRES MEMCACHED RABBITMQ REDIS; do echo "ZULIP__${s}_PASSWORD=$(openssl rand -hex 24)"; done
  echo "ZULIP__SECRET_KEY=$(openssl rand -hex 32)"
  echo "ZULIP__EMAIL_PASSWORD=SMTP_PASSWORT"
} | sudo tee .env > /dev/null
sudo chmod 600 .env

Das PostgreSQL-Passwort wird nur beim ersten Start der Datenbank verwendet. Wer es später ändert, muss es laut Doku zusätzlich per ALTER ROLE in der Datenbank setzen, sonst kann sich Zulip nicht mehr anmelden.

Verifizieren: sudo wc -l .env zeigt 6. docker compose config --quiet endet ohne Fehler. Achtung: Auch eine leere .env besteht diese Prüfung, kontrollieren Sie daher die Zeilenzahl.

Schritt 3: compose.override.yaml konfigurieren

Ergänzen Sie in der kopierten Datei im Block services.zulip die Pflichtwerte, die Proxy-Vertrauensstellung, SMTP und die Ports. Der obere Block secrets: aus der Beispieldatei bleibt unverändert.

services:
  zulip:
    ports: !override
      - name: http
        target: 80
        published: 8082
        host_ip: 127.0.0.1
        app_protocol: http
    environment:
      SETTING_EXTERNAL_HOST: "chat.example.com"
      SETTING_ZULIP_ADMINISTRATOR: "it@example.com"
      TRUST_GATEWAY_IP: True
      SETTING_EMAIL_HOST: "smtp.example.com"
      SETTING_EMAIL_HOST_USER: "noreply@example.com"
      SETTING_EMAIL_PORT: "587"
      SETTING_EMAIL_USE_TLS: True
    secrets: []

Warum !override: Compose hängt Listen aus der Override-Datei an die Originalliste an. Ohne den Tag würde Docker weiterhin die Ports 25, 80 und 443 des Hosts belegen. Mit !override bleibt nur Port 8082 an 127.0.0.1. Port 25 brauchen Sie nur für das Mail-Gateway, das eingehende Mails in Nachrichten umwandelt.

Ohne CERTIFICATES liefert der Container unverschlüsseltes HTTP auf Port 80 und erwartet einen TLS-Proxy davor. TRUST_GATEWAY_IP: True lässt Zulip der Docker-Gateway-Adresse vertrauen, über die ein Proxy auf demselben Host zugreift. Läuft der Proxy auf einem anderen Rechner, setzen Sie stattdessen LOADBALANCER_IPS mit dessen Adresse.

Verifizieren: docker compose config | grep -A4 'published' zeigt nur host_ip: 127.0.0.1 und published: "8082". docker compose config | grep SETTING_EXTERNAL_HOST zeigt Ihren Namen.

Schritt 4: Erstinitialisierung und Start

cd /opt/docker-zulip
docker compose pull
docker compose run --rm zulip app:init
docker compose up -d --wait
docker compose ps

app:init startet die Abhängigkeiten, prüft die Konfiguration, legt die Datenbank an und endet mit der Zeile === End Initial Configuration Phase ===. Fehlt diese Zeile, lesen Sie die Ausgabe auf Warnungen. up --wait kehrt erst zurück, wenn der Healthcheck des Containers erfolgreich ist.

Im Test dauerte der Download rund 90 Sekunden, app:init weitere 50 Sekunden mit Zeilen wie Applying zerver.0663_realm_enable_guest_user_dm_warning... OK. Nach up --wait war der Container rund 40 Sekunden später healthy.

Verifizieren: docker compose ps zeigt zulip als healthy. curl -s -o /dev/null -w '%{http_code}\n' -H 'Host: chat.example.com' -H 'X-Forwarded-Proto: https' http://127.0.0.1:8082/login/ liefert 200.

Schritt 5: Reverse Proxy mit HTTPS

Zulip verlangt HTTPS. Der Proxy muss X-Forwarded-For und X-Forwarded-Proto setzen und lange Verbindungen für die Ereignis-Schnittstelle zulassen. Ein Nginx-Beispiel auf Basis der Zulip-Doku zu Reverse Proxys:

server {
    listen 443 ssl;
    http2 on;
    server_name chat.example.com;
    ssl_certificate     /etc/letsencrypt/live/chat.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/chat.example.com/privkey.pem;
    client_max_body_size 100m;

    location / {
        proxy_pass http://127.0.0.1:8082;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_buffering off;
        proxy_read_timeout 20m;
    }
}

Ohne client_max_body_size scheitern laut Zulip-Doku größere Uploads am Proxy. Passen Sie den Wert an Ihr Upload-Limit an. Alternativ übernimmt der Container TLS selbst: CERTIFICATES: certbot holt ein Let's-Encrypt-Zertifikat und erneuert es automatisch. Dann muss Port 443 des Containers öffentlich erreichbar sein und die !override-Liste Port 443 statt 80 enthalten. Grundlagen zu Zertifikaten finden Sie in Nginx als Reverse Proxy mit TLS.

Verifizieren: sudo nginx -t meldet syntax is ok. curl -sI https://chat.example.com/login/ liefert HTTP/2 200.

Schritt 6: Organisation anlegen

Zulip legt Organisationen über einen Einmal-Link an. Das mitgelieferte Skript manage.py im Repository ruft den Verwaltungsbefehl im laufenden Container auf:

cd /opt/docker-zulip
./manage.py generate_realm_creation_link
Please visit the following secure single-use link to register your
new Zulip organization:

    https://chat.example.com/new/...

Öffnen Sie den Link im Browser, geben Sie Name und Adresse der Organisation ein und legen Sie das erste Konto an. Es wird Eigentümer der Organisation. Einladungen, Registrierungsregeln und Authentifizierung stellen Sie danach unter Einstellungen, Organisation ein.

Verifizieren: Der Link öffnet ein Formular mit den Feldern für Organisationsname, Adresse und E-Mail. Nach der Bestätigungsmail gelingt die Anmeldung, und im Kanal „general“ lässt sich eine Nachricht mit Thema senden.

Schritt 7: Backup und Update

Laut Doku ist das Volume zulip die Einheit der Sicherung. Es enthält Uploads, Zertifikate, die Datei zulip-secrets.conf und unter /data/backups/ einen Datenbank-Dump. app:backup läuft standardmäßig täglich, vor einem Snapshot aktualisieren Sie den Dump von Hand:

cd /opt/docker-zulip
docker compose exec zulip /sbin/entrypoint.sh app:backup
docker compose run --rm -v zulip:/data -v "$(pwd)":/backup zulip tar czf /backup/backup.tar.gz -C /data .
sudo cp .env compose.override.yaml /sicherer/ort/

Das PostgreSQL-Datenverzeichnis im Volume postgresql-14 ist im laufenden Betrieb nicht konsistent kopierbar, maßgeblich ist der Dump. Die .env mit den Geheimnissen sichern Sie getrennt und geschützt. Wiederhergestellt wird laut Doku mit app:restore <dateiname>, entweder auf einem frischen Host nach dem Entpacken des Volumes oder direkt im laufenden Container.

Im Test meldete app:backup Backup process succeeded., das Archiv enthielt zulip-secrets.conf und den Dump unter backups/. Eine vor dem Backup angelegte Markierungstabelle wurde nach dem Löschen per app:restore vollständig zurückgeholt, der Container startete danach selbst neu und die Anmeldeseite lieferte wieder HTTP 200.

Ein Update wechselt den Git-Tag und damit den Image-Tag. Beim ersten Start migriert der Container die Datenbank:

cd /opt/docker-zulip
git fetch --tags
git checkout -B release NEUER_TAG
diff compose.override.yaml.example compose.override.yaml
docker compose pull
docker compose up -d

Ein Downgrade verlangt laut Doku ein manuelles Zurücksetzen der Migrationen mit manage.py migrate. Planen Sie Updates deshalb mit einem frischen Backup.

Verifizieren: tar tzf backup.tar.gz | grep backups/ listet mindestens eine Dump-Datei. Nach einem Update zeigt docker compose images zulip den neuen Tag und der Container wird wieder healthy.

Typische Fehler

SymptomUrsacheLösung
Bind for 0.0.0.0:80 failed: port is already allocatedports in der Override-Datei ohne !override, Liste wird angehängtports: !override wie in Schritt 3
docker compose config meldet keinen Fehler, obwohl die .env leer istLeere Variablen gelten als gültige SecretsSchritt 2 wiederholen, Zeilen der .env zählen
Anmeldung der App an PostgreSQL schlägt fehlZULIP__POSTGRES_PASSWORD nach dem ersten Start geändertAltes Passwort zurück oder ALTER ROLE zulip WITH PASSWORD laut Doku
Container bricht mit Hinweis auf umbenannte Variable abVariablen des alten Images wie DB_HOST oder DISABLE_HTTPSNach der Doku „Upgrading from legacy“ umbenennen
Falsche Client-IP in Logs, Weiterleitungen auf HTTPProxy nicht vertrauenswürdig oder X-Forwarded-Proto fehltTRUST_GATEWAY_IP oder LOADBALANCER_IPS setzen, Header im Proxy prüfen
Keine EinladungsmailsSMTP fehlt oder falsch./manage.py send_test_email it@example.com ausführen und Ausgabe prüfen

Häufige Fragen

Warum nicht die compose.yaml direkt bearbeiten?

Weil ein Update per git checkout sie ersetzt. Eigene Werte in der ignorierten compose.override.yaml bleiben dabei erhalten.

Gibt es mobile Push-Benachrichtigungen?

Ja, über den Push-Dienst von Zulip. Dafür setzen Sie SETTING_ZULIP_SERVICE_PUSH_NOTIFICATIONS und registrieren den Server mit ./manage.py register_server. Das überträgt Metadaten an den Dienst des Herstellers, prüfen Sie das vorab datenschutzrechtlich.

Kann ich eine vorhandene PostgreSQL-Instanz nutzen?

Ja, die Doku beschreibt unter „Existing services“, wie Sie den mitgelieferten Datenbankdienst mit !reset entfernen und SETTING_REMOTE_POSTGRES_HOST setzen.

Fazit

Der offizielle Docker-Weg von Zulip ist mit 12.x deutlich klarer geworden: fester Release-Tag im Git-Repository, eigene Werte in compose.override.yaml, Geheimnisse als Docker Secrets. Mit lokal gebundenem Port, einem TLS-Proxy, funktionierendem SMTP und einer regelmäßigen Sicherung des Volumes zulip samt .env steht eine gut wartbare Chat-Plattform, deren Themenstruktur gerade in verteilten Teams den Überblick erleichtert.

Weiterführende Anleitungen und Quellen