Chatwoot mit Docker auf einem VPS selbst hosten
Installieren Sie Chatwoot mit Docker Compose und Traefik: feste Tags, funktionierendes SMTP, Backups für Postgres und Uploads sowie sichere Upgrades.
Was Sie erstellen
Für das Self-Hosting von Chatwoot auf einem VPS führen Sie vier Container aus: einen Rails-Webprozess, einen Sidekiq-Hintergrund-Worker, PostgreSQL mit der Erweiterung pgvector und Redis. Chatwoot ist ein Open-Source-Kundensupportsystem. Sie erhalten damit einen gemeinsamen Team-Posteingang und ein Website-Chat-Widget auf einem von Ihnen kontrollierten Server. Die Installation dauert etwa zwanzig Minuten. Ob das System auch in einem Jahr noch läuft, hängt von allem ab, was danach kommt: E-Mail-Zustellung, Backups, Upgrades und die Dimensionierung.
Jeder Container hat eine Aufgabe. Rails stellt das Agent-Dashboard und die Widget-API (Application Programming Interface) bereit. Sidekiq führt zeitaufwendige Aufgaben aus: E-Mails senden, verbundene Kanäle abfragen, Automatisierungsregeln ausführen und Berichte erstellen. Postgres speichert Unterhaltungen, Kontakte, Agentenkonten und jede Einstellung, die Sie im Dashboard ändern. Redis speichert die Sidekiq-Warteschlangen und den ActionCable-Pub/Sub-Kanal, der eine neue Nachricht ohne Neuladen der Seite in ein geöffnetes Dashboard überträgt. Redis ist hier kein entbehrlicher Cache. Wenn Redis verloren geht, gehen auch die eingereihten Jobs verloren.
Das Postgres-Image in der vorgelagerten Compose-Datei ist pgvector/pgvector:pg16 und nicht das Standard-Image postgres, weil das Schema von Chatwoot für seine KI-Funktionen die Erweiterung vector aktiviert. Wenn Sie stattdessen das Standard-Postgres verwenden, bricht der erste Datenbankstart mit ERROR: extension "vector" is not available ab, weil die Steuerdatei der Erweiterung in diesem Image fehlt. Verwenden Sie das Image, das upstream bereitstellt.
Diese Anleitung setzt voraus, dass Docker und ein Reverse Proxy auf dem Server bereits funktionieren. Falls nicht, beginnen Sie mit Docker Compose auf einem VPS und kehren Sie anschließend hierher zurück.
Wie viel VPS benötigt ein selbst gehostetes Chatwoot?
Seit August 2026 fordert die Upstream-Anforderungsseite mindestens 4 GB RAM und 4 CPU-Kerne und gibt dafür bis zu 10.000 Unterhaltungen pro Tag an. Für 8 GB RAM und 8 Kerne nennt sie bis zu 20.000 Unterhaltungen pro Tag. Außerdem sind mindestens 1 GB Swap erforderlich. Als Grund wird direkt genannt, dass dem System während eines Upgrades nicht der Speicher ausgeht. Planen Sie für PostgreSQL zunächst 5 GB bis 10 GB Speicherplatz ein, bevor Sie Datei-Uploads berücksichtigen.
Nun zum entscheidenden Punkt. Ein VPS mit 2 GB startet Chatwoot. Mit zwei Agents und einem wenig ausgelasteten Posteingang wirkt alles zunächst unauffällig. Unter zwei Bedingungen fällt das System aus. Die erste ist Sidekiq. Upstream misst den Speicherverbrauch auf einem ausgelasteten Server mit mehr als 1 GB. Ein E-Mail-Schub oder ein Report-Job überschreitet dadurch den verfügbaren Speicher, bevor Rails, PostgreSQL und Redis ihren Anteil belegen. Die zweite Bedingung ist das Upgrade. Dabei db:chatwoot_prepare einen neuen Rails-Prozess, um Migrationen anzuwenden. Der Start von Rails benötigt auf diesem Image mehrere hundert Megabytes, bevor der Prozess produktive Arbeit ausführt.
Sie erhalten vorher keine zuverlässige Warnung. Der Out-of-Memory-Killer des Kernels sendet SIGKILL an den größten Prozess. Docker erkennt, dass der Container beendet wurde, und restart: always ihn erneut. docker compose ps zeigt anschließend einen Container, der immer wieder zu Exited (137) zurückkehrt. Der Wert 137 bedeutet, dass der Prozess durch Signal 9 beendet wurde. Bestätigen Sie dies mit sudo dmesg -T | grep -i "killed process". Der Befehl nennt den Prozess, den der Kernel ausgewählt hat.
Wenn 4 GB das Budget überschreiten, können Sie ein System mit 2 GB und 2 GB Swap betreiben. Akzeptieren Sie dann, dass sich die Antwortzeiten unter Last verschlechtern, statt dass der Dienst vollständig ausfällt. Eine feste Speicherobergrenze pro Dienst ist in jedem Fall sinnvoll. Dadurch kann der Worker die Datenbank nicht mit in den Ausfall reißen. Siehe Speicherlimits in Docker Compose.
Datei-Uploads wachsen ohne eine von Ihnen gesetzte Obergrenze. Jeder Screenshot, den ein Kunde anhängt, landet im Speicher-Volume und bleibt dort. Überwachen Sie daher docker system df -v, statt anzunehmen, dass die Datenbank den Speicherplatz belegt hat.
Compose-Datei abrufen und einen Versions-Tag festlegen
mkdir -p ~/chatwoot && cd ~/chatwoot
wget -O .env https://raw.githubusercontent.com/chatwoot/chatwoot/develop/.env.example
wget -O docker-compose.yaml https://raw.githubusercontent.com/chatwoot/chatwoot/develop/docker-compose.production.yaml
chmod 600 .envIn der gerade heruntergeladenen Datei steht image: chatwoot/chatwoot:latest. Ändern Sie diesen Wert, bevor Sie etwas anderes tun.
services:
base: &base
image: chatwoot/chatwoot:v4.16.2
env_file: .env
volumes:
- storage_data:/app/storagelatest bedeutet, dass docker compose pull beim nächsten Abruf die Version bereitstellt, die an diesem Morgen veröffentlicht wurde. Dabei kann es sich um eine Hauptversion mit Migrationen handeln, über die Sie noch nichts gelesen haben. Chatwoot-Migrationen lassen sich in der Praxis nicht rückgängig machen. Ein versehentliches Versions-Upgrade erfordert daher eine Wiederherstellung aus dem Backup und kein einfaches Zurücksetzen. Legen Sie den Tag fest und ändern Sie ihn nur bewusst. v4.16.2 war im August 2026 die aktuelle Version. Prüfen Sie auf der Release-Seite, welchen Tag Sie heute festlegen sollten.
Der Dienst base ist ein YAML-Anker, den sowohl rails als auch sidekiq zusammenführen. Wenn Sie den Tag an dieser Stelle ändern, gilt die Änderung für beide. Löschen Sie in der Datei außerdem die version: '3'-Zeile am Anfang. Moderne Compose-Versionen ignorieren sie und geben bei jedem Befehl the attribute 'version' is obsolete, it will be ignored aus.
Die .env-Datei ausfüllen
Generieren Sie zuerst das Secret. Upstream erwartet einen alphanumerischen Wert, weil Sonderzeichen verändert werden können, wenn der Wert eine Shell oder einen YAML-Parser durchläuft.
head /dev/urandom | tr -dc A-Za-z0-9 | head -c 63 ; echo ''Setzen Sie anschließend diese Schlüssel in .env.
SECRET_KEY_BASE=<the 63 characters you just generated>
FRONTEND_URL=https://support.example.com
FORCE_SSL=true
DEFAULT_LOCALE=en
ENABLE_ACCOUNT_SIGNUP=true
POSTGRES_HOST=postgres
POSTGRES_USERNAME=postgres
POSTGRES_PASSWORD=<long random string>
POSTGRES_DATABASE=chatwoot
REDIS_URL=redis://redis:6379
REDIS_PASSWORD=<a different long random string>
RAILS_ENV=production
INSTALLATION_ENV=docker
ACTIVE_STORAGE_SERVICE=localPOSTGRES_HOST=postgres und redis://redis:6379 sind die Compose-Dienstnamen, die im standardmäßigen Netzwerk des Projekts aufgelöst werden. FRONTEND_URL ist keine reine Dekoration. Chatwoot erstellt daraus die URL für das Widget-Skript und jeden Link in einer ausgehenden E-Mail. Ein falscher Wert führt daher zu Links für die Passwortzurücksetzung, die auf einen Host zeigen, der nicht antwortet.
Die eigentliche Falle befindet sich in der Upstream-Datei. Der Dienst postgres liest .env nicht ein. Er enthält einen eigenen environment-Block, in dem POSTGRES_PASSWORD= leer bleibt. Wenn Sie das Passwort nur in .env setzen, hat die Datenbank daher kein Passwort, während die Anwendung eines verwendet. Verweisen Sie den Dienst auf dieselbe Variable:
postgres:
image: pgvector/pgvector:pg16
restart: always
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
- POSTGRES_DB=chatwoot
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}Compose liest .env aus dem Projektverzeichnis für die ${...}-Substitution. Dadurch erhalten beide Seiten dieselbe Zeichenfolge. Bei einer falschen Konfiguration beendet sich Rails mit PG::ConnectionBad: FATAL: password authentication failed for user "postgres".
Ein Verhalten überrascht fast alle: Das Postgres-Image wendet POSTGRES_PASSWORD nur an, wenn es ein leeres Datenverzeichnis initialisiert. Eine spätere Änderung hat keine Wirkung, weil initdb nicht erneut ausgeführt wird. Wenn Sie den Stack bereits einmal gestartet haben, ändern Sie den Wert direkt in der Datenbank.
docker compose exec postgres psql -U postgres -c "ALTER USER postgres WITH PASSWORD 'the-new-password';"ENABLE_ACCOUNT_SIGNUP=true ist nur vorübergehend erforderlich. Damit wird das öffentliche Registrierungsformular geöffnet, damit Sie das erste Konto erstellen können. Setzen Sie den Wert auf false und führen Sie docker compose up -d erneut aus, sobald Ihr Konto existiert. Andernfalls kann sich jeder, der die URL findet, in Ihrem Supportsystem registrieren. Danach werden Agents per Einladung hinzugefügt. Ihre Passwörter werden ausschließlich in dieser Anwendung gespeichert. Das funktioniert problemlos, bis Sie etwa ein halbes Dutzend Dienste betreiben und die separate Kontoliste in jedem Dienst verwalten müssen. Dann ist ein selbst gehosteter Identity Provider wie Authentik die Komponente, die diese Kontolisten ersetzt.
.env enthält jetzt alle Secrets dieses Stacks im Klartext. Setzen Sie daher den Modus auf 600 und nehmen Sie die Datei aus git aus. Wie Compose Env-Dateien liest und wo Secrets offengelegt werden behandelt die kritischen Punkte, einschließlich des Unterschieds zwischen env_file und environment.
Chatwoot hinter Ihrem bestehenden Traefik betreiben
Bauen Sie keinen zweiten Reverse Proxy für eine einzelne Anwendung. Wenn Traefik auf diesem Server bereits für andere Container TLS (Transport Layer Security) terminiert, wird Chatwoot über einen Label-Block eingebunden. Falls das noch nicht eingerichtet ist, richten Sie es einmal mit Traefik vor mehreren Docker-Compose-Anwendungen ein und kehren Sie anschließend hierher zurück.
Belassen Sie die docker-compose.yaml-Datei des Upstream möglichst unverändert, damit Sie sie später mit einer neueren Version vergleichen können, und legen Sie Ihre Änderungen in einer Override-Datei ab. Compose führt docker-compose.override.yaml automatisch zusammen. Compose auf mehrere Dateien aufteilen erläutert die Regeln für diese Zusammenführung.
services:
rails:
networks:
- default
- proxy
labels:
- "traefik.enable=true"
- "traefik.http.routers.chatwoot.rule=Host(`support.example.com`)"
- "traefik.http.routers.chatwoot.entrypoints=websecure"
- "traefik.http.routers.chatwoot.tls.certresolver=letsencrypt"
- "traefik.http.services.chatwoot.loadbalancer.server.port=3000"
networks:
proxy:
external: trueVerwenden Sie Ihre eigenen Namen für Entrypoint und Certresolver. Der Container muss sich im selben Docker-Netzwerk wie Traefik befinden. Dafür sorgt der Eintrag proxy. Außerdem muss er weiterhin in default eingebunden sein, sonst verliert er den Zugriff auf Postgres und Redis. Diese zweite Zeile wird häufig vergessen.
Lassen Sie den Block ports: unverändert. Upstream bindet ihn an 127.0.0.1:3000, also ausschließlich an die Loopback-Schnittstelle. Dadurch ist er nicht aus dem Internet erreichbar und bleibt für Tests innerhalb des Servers mit curl -I http://127.0.0.1:3000 nutzbar.
Das Agent-Dashboard hält für die Live-Zustellung von Nachrichten eine WebSocket-Verbindung zu /cable offen. Traefik leitet das HTTP-Upgrade ohne zusätzliche Konfiguration weiter. Sie müssen daher nichts ergänzen. Wenn Sie später einen CDN oder einen weiteren Proxy vor Traefik setzen, müssen Sie dort WebSockets erlauben. Andernfalls lädt das Dashboard zwar normal, neue Nachrichten erscheinen jedoch erst nach einer manuellen Aktualisierung.
Datenbank initialisieren und den Stack starten
Starten Sie zuerst die Datendienste, und warten Sie, bis Postgres den ersten Start abgeschlossen hat.
docker compose up -d postgres redis
docker compose logs postgres | tail -n 5Warten Sie auf database system is ready to accept connections. Erstellen Sie anschließend das Schema.
docker compose run --rm rails bundle exec rails db:chatwoot_prepareDamit wird die Datenbank erstellt, falls sie fehlt. Anschließend werden das Schema und die standardmäßigen Seed-Daten geladen. Der Befehl gibt Migrationszeilen aus und wird fehlerfrei beendet. Wenn kontinuierlich postgres:5432 - no response ausgegeben wird, wartet der Entrypoint auf eine Datenbank, die noch keine Verbindungen annimmt. Beim ersten Start bedeutet das normalerweise, dass initdb noch arbeitet. Warten Sie, lesen Sie die Postgres-Logs und führen Sie den Befehl anschließend erneut aus. Wenn der Vorgang bei der Erweiterung vector stoppt, haben Sie das pgvector-Image durch das Standard-Postgres-Image ersetzt.
docker compose up -d
docker compose ps
docker compose logs --tail 30 railsBei allen vier Containern sollte Up angezeigt werden. Das Rails-Log sollte mit einer Puma-Zeile enden, die auf http://0.0.0.0:3000 lauscht. Prüfen Sie anschließend den öffentlichen Pfad:
curl -sI https://support.example.com | head -n 1HTTP/2 200 bedeutet, dass die gesamte Kette funktioniert. Ein 404 von Traefik bedeutet, dass die Router-Regel nicht zutraf, meistens wegen eines Tippfehlers im Hostnamen. Ein 502 bedeutet, dass Traefik den Router gefunden hat, den Container aber nicht erreichen konnte. Fast immer fehlt dann das proxy-Netzwerk oder es ist ein loadbalancer.server.port konfiguriert, das nicht 3000 entspricht.
Öffnen Sie die URL, erstellen Sie Ihr Konto unter /app/auth/signup, setzen Sie anschließend ENABLE_ACCOUNT_SIGNUP=false und führen Sie docker compose up -d aus, um das Formular zu schließen.
Warum das Zurücksetzen von Passwörtern und E-Mail-Konversationen ohne SMTP fehlschlagen
Chatwoot ohne SMTP-Einstellungen (Simple Mail Transfer Protocol) ist ein Supportsystem, das keine E-Mails senden kann. Dadurch funktionieren nicht nur Benachrichtigungen nicht. Das Zurücksetzen von Passwörtern schlägt fehl. Ein Administrator, der gesperrt ist, bleibt dadurch gesperrt. Einladungen für Agenten funktionieren ebenfalls nicht, weil eine Einladung per E-Mail gesendet wird. Antworten an Kunden in einer E-Mail-Konversation funktionieren nicht mehr. Die Konversation läuft dann nur in eine Richtung. Diesen Schritt überspringen viele und bemerken das Problem erst in einer besonders ungünstigen Situation.
Der Mechanismus ist einfach. Ohne SMTP-Einstellungen verwendet ActionMailer weiterhin den Standardwert und stellt die Zustellung an localhost über Port 25 zu. Im Rails-Container läuft kein Mailserver. Deshalb erzeugt der Zustellungsjob Errno::ECONNREFUSED: Connection refused - connect(2) for "localhost" port 25. E-Mails werden von einem Hintergrundjob gesendet. Die betreffende Meldung steht daher im Sidekiq-Log und nicht im Rails-Log. Gleichzeitig sieht die Person, die auf „Passwort vergessen“ klickt, eine Bestätigung, erhält aber keine E-Mail.
MAILER_SENDER_EMAIL=Support <support@example.com>
SMTP_DOMAIN=example.com
SMTP_ADDRESS=smtp.example.com
SMTP_PORT=587
SMTP_USERNAME=support@example.com
SMTP_PASSWORD=<the relay password>
SMTP_AUTHENTICATION=plain
SMTP_ENABLE_STARTTLS_AUTO=trueVerwenden Sie Port 587 mit STARTTLS. Dabei wird die Verbindung zunächst unverschlüsselt aufgebaut und vor der Authentifizierung auf eine verschlüsselte Verbindung hochgestuft. Die meisten VPS-Anbieter blockieren ausgehenden Datenverkehr über Port 25, um Spam zu begrenzen. Deshalb ist ein Relay über Port 587 normalerweise die einzige Verbindung, die überhaupt funktioniert. SMTP_DOMAIN ist die Domain, die Ihr Server während der SMTP-Kommunikation ankündigt. Einige Relays lehnen eine nicht übereinstimmende Domain ab.
Übernehmen Sie die Einstellungen und überwachen Sie den Worker:
docker compose up -d rails sidekiq
docker compose logs -f sidekiqLösen Sie auf der Anmeldeseite das Zurücksetzen eines Passworts aus. Bei einer funktionierenden Zustellung wird der Mailer-Job im Sidekiq-Log normal beendet. Bei einem Fehler wird zuerst die Exception-Klasse angezeigt. Danach wiederholt Sidekiq den Versuch mit zunehmend längeren Wartezeiten. Deshalb erzeugt ein fehlerhaftes Relay über Stunden alle paar Minuten denselben Fehler.
Zwei Ablehnungen treten häufig auf. Keine davon ist ein Fehler in Chatwoot. 535 Authentication failed bedeutet, dass der Benutzername oder das Passwort für dieses Relay falsch ist. Viele Anbieter verlangen statt des Kontopassworts ein Anwendungspasswort. 550 Sender address rejected bedeutet, dass MAILER_SENDER_EMAIL eine Absenderadresse ist, die das Relay nicht akzeptiert. Daher muss es sich um ein Postfach oder eine Domain handeln, die Sie beim Anbieter verifiziert haben.
Der Empfang von E-Mails in einer Konversation ist ein separater Vorgang. Er benötigt MAILER_INBOUND_EMAIL_DOMAIN und RAILS_INBOUND_EMAIL_SERVICE sowie einen Mailserver, der eingehende Nachrichten an Chatwoot übergibt. Ein Relay zu mieten, ist der schnellste Weg. Wenn Sie den gesamten Mailpfad selbst betreiben möchten, beschreibt der Betrieb eines eigenen Mailservers mit Mailcow, was dieser Aufwand tatsächlich umfasst.
Was Sie sichern müssen und wie Sie die Wiederherstellung nachweisen
Ein Chatwoot-Backup besteht aus vier Teilen. Fehlt einer davon, wird die Wiederherstellung zu einer Neuinstallation.
- Die Postgres-Datenbank. Sie enthält Unterhaltungen, Kontakte, Agent-Konten und alle Einstellungen.
- Das
storage_data-Volume.ACTIVE_STORAGE_SERVICE=localschreibt hochgeladene Dateien auf die Festplatte und speichert in Postgres nur eine Referenzzeile. - Die
.env-Datei. Sie enthältSECRET_KEY_BASEund dieACTIVE_RECORD_ENCRYPTION_*-Schlüssel. - Die Compose-Dateien. Sie dokumentieren den exakten Image-Tag, zu dem das Datenbankschema passt.
Wenn Sie nur die Datenbank wiederherstellen, sind alle Unterhaltungen wieder vorhanden, aber die Anhänge funktionieren nicht. Die Datensätze verweisen auf Dateien, die nicht mehr auf der Festplatte liegen.
cd ~/chatwoot
docker compose exec -T postgres pg_dump -U postgres -Fc chatwoot > db-$(date +%F).dump-T ist wichtig. Ohne diese Option weist Compose ein Pseudo-Terminal zu. Dadurch werden Newline-Bytes im Datenstrom geändert. Die resultierende Dump-Datei wird von pg_restore abgelehnt. -Fc ist das benutzerdefinierte Format. Es komprimiert die Daten und ermöglicht es pg_restore, selektiv zu arbeiten.
docker run --rm -v chatwoot_storage_data:/data:ro -v "$PWD":/backup alpine \
tar czf /backup/storage-$(date +%F).tgz -C /data .Der Volume-Name besteht aus dem Namen Ihres Projektverzeichnisses und _storage_data. Bestätigen Sie ihn mit docker volume ls | grep storage_data, bevor Sie diesem Befehl vertrauen. Docker erstellt ein leeres Volume, anstatt mit einem Fehler abzubrechen, wenn Sie ein nicht vorhandenes Volume angeben. Sie erhalten dann ein gültiges, leeres Archiv und überhaupt keinen Fehler. Prüfen Sie anschließend die Größe mit ls -lh storage-*.tgz.
Beide Dateien liegen nun auf derselben Festplatte wie die Daten, die sie schützen sollen. Das schützt Sie vor nichts. Übertragen Sie die Dateien auf ein anderes System und verschlüsseln Sie sie. Ein Datenbank-Dump enthält jede Kundennachricht im Klartext. Verschlüsselte Offsite-Backups mit restic behandelt die Planung und Aufbewahrung.
Der Wiederherstellungstest: Führen Sie ihn durch, bevor Sie ihn benötigen
Stellen Sie die Daten auf einem zweiten VPS wieder her, nicht auf dem produktiven System. Kopieren Sie .env, die Compose-Dateien und beide Archive auf das zweite System und führen Sie anschließend Folgendes aus:
docker compose up -d postgres
docker compose exec -T postgres pg_restore -U postgres -d chatwoot --clean --if-exists < db-2026-08-10.dump
docker run --rm -v chatwoot_storage_data:/data -v "$PWD":/backup alpine \
sh -c 'rm -rf /data/* && tar xzf /backup/storage-2026-08-10.tgz -C /data'
docker compose up -d--clean --if-exists löscht die vorhandenen Objekte vor dem Laden. Verwenden Sie den Befehl daher nur für eine Datenbank, deren Inhalt Sie löschen können. Melden Sie sich anschließend an und öffnen Sie eine Unterhaltung mit einem Anhang. Wenn die Nachrichtenliste geladen wird und sich die Datei herunterladen lässt, ist das Backup brauchbar.
Eine Wiederherstellung mit einem anderen SECRET_KEY_BASE macht alle Sitzungscookies ungültig. Dadurch werden alle Benutzer abgemeldet. Eine Wiederherstellung mit anderen ACTIVE_RECORD_ENCRYPTION_*-Schlüsseln ist noch problematischer: Chatwoot kann die Spalten mit den Zugangsdaten der Kanäle nicht entschlüsseln und meldet ActiveRecord::Encryption::Errors::Decryption. Deshalb steht .env auf der Backup-Liste.
So aktualisieren Sie Chatwoot auf einen neuen Tag
Die Reihenfolge ist wichtiger als die einzelnen Befehle.
- Lesen Sie die Release Notes zwischen Ihrem Tag und dem Ziel-Tag. Achten Sie dabei auf erforderliche manuelle Schritte.
- Erstellen Sie einen aktuellen Datenbank-Dump und ein Archiv des Speichers. Prüfen Sie, ob die Dateigrößen plausibel sind.
- Ändern Sie den Image-Tag für den Dienst
baseindocker-compose.yaml. - Rufen Sie das neue Image ab, stoppen Sie den Stack, führen Sie die Migrationen aus und starten Sie den Stack anschließend erneut.
docker compose pull
docker compose down
docker compose run --rm rails bundle exec rails db:chatwoot_prepare
docker compose up -d
docker compose imagesRufen Sie das Image vor der Migration ab. Die Migration muss aus dem neuen Image ausgeführt werden, weil das alte Image die neuen Migrationsdateien nicht enthält. Stoppen Sie den Stack vor der Migration. Der alte Code und das neue Schema sind nicht kompatibel. Ein laufender alter Rails-Prozess kann daher Fehler auslösen oder Datensätze schreiben, die das neue Schema nicht akzeptiert. Das Stoppen gibt außerdem den Arbeitsspeicher frei, den die Migration benötigt. Aus diesem Grund fordert Upstream Swap an.
docker compose images gibt den Tag aus, den jeder Container tatsächlich verwendet. Damit erkennen Sie den Fall, dass Sie den Tag geändert, das Image aber nicht abgerufen haben.
Überspringen Sie nicht viele Versionen auf einmal. Upstream empfiehlt bei einer alten Installation, schrittweise über Zwischen-Tags zu aktualisieren. Migrationen werden entfernt, sobald sie in das Basisschema übernommen wurden. Eine sehr alte Datenbank kann dadurch einen Zustand erreichen, für den kein weiterer Migrationspfad existiert. Aktualisieren Sie jeweils um eine Minor-Version und führen Sie nach jeder Version den Prepare-Schritt aus.
Wenn Rails startet, bevor die Migration ausgeführt wurde, verweigert der Dienst die Anfragen und protokolliert ActiveRecord::PendingMigrationError: Migrations are pending. Wenn restart: always gesetzt ist, wird der Container anschließend wiederholt neu gestartet. Dadurch zeigt docker compose ps eine Uptime an, die sich alle paar Sekunden zurücksetzt. Führen Sie den Prepare-Schritt aus. Dadurch wird das Problem behoben.
Für ein Rollback setzen Sie den alten Tag wieder ein und stellen den Dump wieder her. Es gibt keinen verlässlichen Pfad für Rückwärtsmigrationen. Genau dafür ist Schritt 2 erforderlich.
Fehlerbilder und die angezeigten Meldungen
502 Bad Gateway von Traefik. Der Router wurde zugeordnet, aber das Backend hat nicht geantwortet. Prüfen Sie mit docker compose ps, ob Rails als Up angezeigt wird. Führen Sie anschließend docker network inspect proxy aus und bestätigen Sie, dass der Rails-Container in der Containerliste erscheint. Ein nicht verbundenes Container-Netzwerk ist für Traefik unsichtbar. Die Anfrage wird dann zwar einem Router zugeordnet, aber nicht weitergeleitet.
Das Dashboard wird geladen, aber neue Nachrichten werden erst nach einer Aktualisierung angezeigt. Die WebSocket-Verbindung zu /cable wird nicht durchgelassen, oder FRONTEND_URL stimmt nicht mit der Adresse in der Browserleiste überein. Bei einer Abweichung versucht die Seite, eine WebSocket-Verbindung zu einer anderen Origin herzustellen. Der Browser blockiert diese Verbindung.
FATAL: password authentication failed for user "postgres". Das Passwort in .env und das im Postgres-Datenvolume gespeicherte Passwort unterscheiden sich. Beheben Sie das Problem mit ALTER USER im laufenden Container. Eine erneute Änderung von .env ändert eine bereits initialisierte Datenbank nicht.
NOAUTH Authentication required. Redis läuft mit --requirepass, aber die Anwendung hat sich ohne Passwort verbunden. Daher fehlt REDIS_PASSWORD in .env oder wurde nicht übernommen. Testen Sie die Verbindung direkt mit docker compose exec redis redis-cli -a "$REDIS_PASSWORD" ping. Die Antwort sollte PONG lauten.
Container werden mit Exit-Code 137 beendet. Das ist SIGKILL. Auf einem kleinen System bedeutet dies, dass der Out-of-Memory-Killer des Kernels den Prozess beendet hat. Fügen Sie Swap hinzu, setzen Sie Speicherlimits pro Dienst oder wechseln Sie zu einem größeren Tarif.
FAQ
Wie viel RAM benötigt ein selbst gehosteter Chatwoot-VPS?
Stand August 2026 fordert Upstream mindestens 4 GB RAM und 4 CPU-Kerne für bis zu 10.000 Unterhaltungen pro Tag sowie 8 GB RAM und 8 Kerne für bis zu 20.000 Unterhaltungen. Fügen Sie mindestens 1 GB Swap hinzu, weil ein Upgrade einen zweiten Rails-Prozess zum Anwenden der Migrationen startet. Dabei geht kleinen Systemen häufig der Speicher aus. Ein VPS mit 2 GB startet und funktioniert für einige Agents. Unter Last kann jedoch allein Sidekiq mehr als 1 GB benötigen. Rechnen Sie daher während stark ausgelasteter Zeiträume und bei Upgrades mit beendeten Containern und Exit-Code 137.
Warum kommen E-Mails zum Zurücksetzen des Chatwoot-Passworts nie an?
Weil keine SMTP-Einstellungen konfiguriert sind. ActionMailer versucht daher, die E-Mail an localhost über Port 25 zuzustellen, aber im Container läuft kein Mailserver. Der Job schlägt in Sidekiq mit Errno::ECONNREFUSED: Connection refused - connect(2) for "localhost" port 25 fehl, während der Browser weiterhin eine Erfolgsmeldung anzeigt. Setzen Sie SMTP_ADDRESS, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD und MAILER_SENDER_EMAIL in .env, starten Sie die Rails- und Sidekiq-Dienste neu und überwachen Sie anschließend docker compose logs -f sidekiq, während Sie das Zurücksetzen auslösen.
Was muss ich sichern, um Chatwoot wiederherzustellen?
Die Postgres-Datenbank, das storage_data-Docker-Volume, die .env-Datei und die Compose-Dateien. Die Datenbank allein reicht nicht aus, weil hochgeladene Dateien im Volume gespeichert sind, während Postgres nur Verweise darauf enthält. Eine Wiederherstellung ausschließlich der Datenbank führt daher zu Unterhaltungen mit beschädigten Anhängen. .env ist wichtig, weil ein anderes SECRET_KEY_BASE alle Benutzer abmeldet. Andere ACTIVE_RECORD_ENCRYPTION_*-Schlüssel machen die verschlüsselten Spalten unlesbar.
Wie aktualisiere ich Chatwoot, ohne die Datenbank zu beschädigen?
Erstellen Sie ein Backup, ändern Sie den Image-Tag in Ihrer Compose-Datei und führen Sie anschließend docker compose pull, docker compose down, docker compose run --rm rails bundle exec rails db:chatwoot_prepare und docker compose up -d aus. Führen Sie zuerst den Pull aus, weil die Migrationen aus dem neuen Image ausgeführt werden müssen. Stoppen Sie außerdem zuerst den Stack, weil alter Code mit einem neuen Schema Fehler verursacht. Aktualisieren Sie bei einer alten Installation jeweils nur um eine Minor-Version. Migrationen werden entfernt, sobald sie in das Basisschema übernommen wurden.
Kann ich statt pgvector das standardmäßige postgres-Image verwenden?
Nein. Das Schema von Chatwoot aktiviert die vector-Erweiterung. Daher schlägt das Standard-postgres-Image während db:chatwoot_prepare mit ERROR: extension "vector" is not available fehl, weil die Steuerdatei der Erweiterung in diesem Image nicht vorhanden ist. Behalten Sie pgvector/pgvector:pg16 aus der Upstream-Compose-Datei bei oder verwenden Sie ein anderes Image, das pgvector für Ihre Postgres-Hauptversion enthält.