Stalwart Mailserver mit Docker Compose installieren
Stalwart bündelt SMTP, IMAP, POP3, JMAP, CalDAV, CardDAV und WebDAV in einem einzigen Serverprozess. Diese Anleitung führt Schritt für Schritt durch die Inbetriebnahme mit Docker Compose, auf Basis eines echten Testlaufs mit Image v0.16 und Version 0.16.23. Mit Test- und Produktionsvariante der compose.yaml, Healthchecks, Rechten, DNS-Einträgen und klarer Trennung zwischen selbst geprüft und nur dokumentiert.

Wer einen eigenen Mailserver betreiben will, kombiniert bisher meist mehrere Dienste: Postfix für SMTP, Dovecot für IMAP, dazu Rspamd, eine Datenbank und ein Webinterface. Stalwart geht einen anderen Weg. Es handelt sich um einen einzigen, in Rust geschriebenen Serverprozess, der SMTP, IMAP, POP3, JMAP sowie CalDAV, CardDAV und WebDAV abdeckt. Statt fünf Dienste zu verdrahten, startet man einen Container.
Diese Anleitung führt durch die Inbetriebnahme von Stalwart mit Docker Compose. Grundlage ist ein echter Testlauf auf einem Ubuntu-Host mit Docker und dem Image stalwartlabs/stalwart:v0.16, das im Container die Version 0.16.23 meldete. Alles, was in diesem Test tatsächlich beobachtet wurde, ist als solches gekennzeichnet. Alles, was nur aus der offiziellen Dokumentation stammt, ebenfalls. Das ist bei einem Mailserver kein Formalismus, sondern Voraussetzung dafür, dass Sie die Risiken einschätzen können.
Was Stalwart ist und wo die Grenzen liegen
Stalwart bündelt die komplette Mail- und Groupware-Funktion in einem Prozess. Der praktische Vorteil liegt weniger in der Sprache Rust als in der reduzierten Betriebskomplexität: eine Konfigurationsquelle, ein Logstrom, ein Healthcheck, ein Update. Es gibt keine Socket-Übergabe zwischen MTA und IMAP-Server, keine separate Spamfilter-Instanz, keine eigene Weboberfläche in einem zweiten Container. Wer schon einmal eine Postfix-Dovecot-Rspamd-Kette nach einem Distributionsupgrade repariert hat, kennt den Wert dieser Reduktion.
Der Projektstand lässt sich belegen. Über die GitHub-API abgerufen am 24.09.2026: das Repository stalwartlabs/stalwart hat 14.782 Sterne, der letzte Push erfolgte am 23.09.2026, das Repository ist nicht archiviert. Das aktuellste Release ist v0.16.23 vom 21.09.2026. Die Entwicklung läuft also aktiv und in kurzen Abständen.
Und jetzt die Gegenseite, ohne Beschönigung. Die Versionsnummer beginnt mit 0. Das ist bei Stalwart keine Koketterie, sondern ein Reifehinweis: Datenformate, Konfigurationsschlüssel und Standardwerte können sich zwischen Minor-Versionen ändern. Für einen Dienst, an dem die Geschäftskommunikation hängt, bedeutet das höhere Pflegelast, nicht weniger. Dazu kommt der Punkt, der unabhängig von der Software gilt: Einen Mailserver selbst zu betreiben ist grundsätzlich anspruchsvoll. Ob Ihre Mails ankommen, entscheidet nicht die Software, sondern ein sauberes DNS-Setup mit SPF, DKIM, DMARC und einem passenden PTR-Eintrag sowie die Reputation Ihrer IP-Adresse. Eine frische IP aus einem Rechenzentrumsblock landet anfangs regelmäßig im Spamordner großer Anbieter, egal wie korrekt die Konfiguration ist.
Was tatsächlich getestet wurde und was nicht
Damit Sie die Aussagen dieser Anleitung gewichten können, hier die klare Trennung.
In der Testumgebung selbst ausgeführt und beobachtet:
- Start des Stacks mit
docker compose up -dund benannten Volumes. - Erreichen des Zustands
Up (healthy)in unter einer Minute. - Start im Bootstrap-Modus samt Logausgabe und einmalig angezeigtem temporären Administrator.
- Nachladen der Weboberfläche aus dem Internet beim Erststart.
- Antwort HTTP 302 mit
Location: /accountauf dem gemappten HTTP-Port. - Beide Healthcheck-Endpunkte
/healthz/readyund/healthz/livemit HTTP 200. - Prozessbenutzer im Container:
uid=2000(stalwart) gid=2000(stalwart). - Versionsabgleich im Container:
stalwart --versionmeldet 0.16.23. - Die Warnung des Servers, dass der konfigurierte DNS-Resolver kein DNSSEC validieren kann.
Nicht getestet, alle Aussagen dazu stammen aus der offiziellen Dokumentation und sind im Text so gekennzeichnet:
- Echter Mailversand und Mailempfang über Port 25 nach außen.
- IMAP-Anmeldung mit einem echten Postfach.
- TLS-Zertifikate per ACME.
- DKIM-Signierung sowie SPF- und DMARC-Auswertung im Betrieb.
- Clustering und externe Speicher-Backends wie PostgreSQL oder S3.
- Backup und Restore der Stalwart-Daten.
- Update von einer älteren Minor-Version.
Voraussetzungen und Ressourcen
- Ein Linux-Host mit Docker Engine und dem Compose-Plugin. Der Test lief auf Ubuntu.
- Eine eigene Domain und vollständige Kontrolle über deren DNS-Einträge. Ohne eigene Zone können Sie SPF, DKIM und DMARC nicht setzen.
- Eine statische öffentliche IP-Adresse mit passendem Reverse-DNS-Eintrag. Viele Empfänger weisen Mail ohne gültigen PTR-Eintrag direkt ab.
- Ein offener Port 25 in beiden Richtungen. Zahlreiche Anbieter sperren ausgehenden SMTP-Verkehr auf Port 25 standardmäßig und geben ihn nur auf Antrag frei. Klären Sie das vor der Installation, nicht danach.
- Internetzugang beim Erststart. Der Server lädt die Weboberfläche zur Laufzeit nach, siehe unten. In abgeschotteten Netzen ohne Ausgang fehlt die Oberfläche.
- Laut offizieller Dokumentation wird Stalwart als Multi-Architektur-Image veröffentlicht, sowohl auf Docker Hub als auch in der GitHub Container Registry. Das Image enthält das Server-Binary und eine minimale Debian-Laufzeit.
Getestete Version und Tag-Strategie
Getestet wurde stalwartlabs/stalwart:v0.16. Laut offizieller Dokumentation stehen vier Tag-Formen zur Verfügung:
| Tag | Bedeutung | Einsatz |
|---|---|---|
latest | folgt dem jeweils neuesten Release | für Produktion nicht empfohlen |
v0.16 | neuester Patch innerhalb der Minor-Serie 0.16 | von der Dokumentation für Produktion empfohlen |
v0.16.23 | exakt diese Patch-Version | wenn Sie jeden Patchwechsel selbst steuern wollen |
edge | neuester Build des main-Branch | nur zum Testen |
Die kurze Form v0.16 ist der sinnvolle Mittelweg: Sie erhalten Fehlerkorrekturen innerhalb der Serie, überspringen aber nicht versehentlich eine Minor-Version mit brechenden Änderungen. Genau deshalb steht sie auch in den Compose-Dateien dieser Anleitung.
Die compose.yaml für den Test
Diese Datei wurde exakt so ausgeführt. Die Hostports sind absichtlich verschoben, damit der Stack auf einem Host mit bereits belegten Mailports startet.
services:
stalwart:
container_name: stalwart
image: stalwartlabs/stalwart:v0.16
restart: unless-stopped
ports:
- "18080:8080"
- "10025:25"
- "10587:587"
- "10143:143"
volumes:
- stalwart-etc:/etc/stalwart
- stalwart-data:/var/lib/stalwart
volumes:
stalwart-etc:
stalwart-data:
Jeder Parameter im Einzelnen:
| Parameter | Bedeutung |
|---|---|
container_name: stalwart | Fester Containername, damit docker exec stalwart ... und docker logs stalwart ohne Nachschlagen funktionieren. |
image: stalwartlabs/stalwart:v0.16 | Pinnt auf die Minor-Serie 0.16. Kein latest, siehe Abschnitt zu Updates. |
restart: unless-stopped | Container startet nach Hostneustart und nach Absturz automatisch, bleibt aber gestoppt, wenn Sie ihn bewusst gestoppt haben. |
18080:8080 | HTTP-Port für Weboberfläche, JMAP und die Healthcheck-Endpunkte. Im Test auf 18080 gemappt. |
10025:25 | SMTP für die Annahme von Mail anderer Mailserver. |
10587:587 | Submission, also der Port, über den Ihre eigenen Nutzer Mail einliefern. |
10143:143 | IMAP mit STARTTLS. |
stalwart-etc:/etc/stalwart | Volume für die Konfigurationsdatei. |
stalwart-data:/var/lib/stalwart | Volume für die Anwendungsdaten, also Postfächer, Indizes und Metadaten. |
Produktive compose.yaml und Portübersicht
Im Produktivbetrieb müssen die Ports auf dem Host den echten Standardports entsprechen, weil fremde Mailserver Sie ausschließlich auf Port 25 erreichen und Clients die üblichen Ports erwarten.
services:
stalwart:
container_name: stalwart
image: stalwartlabs/stalwart:v0.16
restart: unless-stopped
env_file:
- .env
ports:
# SMTP, Annahme von Mail anderer Mailserver
- "25:25"
# Submission mit STARTTLS, Einlieferung eigener Nutzer
- "587:587"
# Submission ueber implizites TLS
- "465:465"
# IMAP mit STARTTLS
- "143:143"
# IMAP ueber implizites TLS
- "993:993"
# POP3 mit STARTTLS
- "110:110"
# POP3 ueber implizites TLS
- "995:995"
# ManageSieve fuer Filterregeln
- "4190:4190"
# HTTPS fuer Weboberflaeche, JMAP, CalDAV, CardDAV, WebDAV
- "443:443"
# HTTP, unter anderem fuer die Healthcheck-Endpunkte
- "8080:8080"
volumes:
- stalwart-etc:/etc/stalwart
- stalwart-data:/var/lib/stalwart
volumes:
stalwart-etc:
stalwart-data:
Sie müssen nicht alle diese Ports veröffentlichen. Entscheidend ist, was der Server leisten soll:
- Öffentlicher Mailserver: Port 25 ist Pflicht, sonst nimmt niemand Mail von Ihnen an und Sie bekommen keine. Dazu 443 für Weboberfläche und Groupware sowie die Clientports, die Ihre Nutzer brauchen.
- Reine interne Installation: Häufig genügen 587 und 993. Interne Anwendungen liefern über Submission ein, Clients lesen per IMAP über TLS. Port 25 bleibt geschlossen, POP3 und ManageSieve ebenso, wenn niemand sie nutzt.
- Hinter einem Reverse Proxy: 443 und 8080 bleiben auf localhost gebunden, der Proxy terminiert TLS. Details im Netzwerkabschnitt.
Ein nicht veröffentlichter Port ist der wirksamste Schutz für einen Dienst, den Sie nicht brauchen. Jede Zeile unter ports ist eine bewusste Entscheidung, keine Formalie.
Die .env-Datei
Legen Sie neben der compose.yaml eine Datei .env an. Sie enthält keine echten Geheimnisse aus dieser Anleitung, sondern Platzhalter, die Sie ersetzen müssen.
# Feste Zugangsdaten fuer den Wiederherstellungs-Administrator.
# Ersetzen Sie den Platzhalter durch ein langes, zufaellig erzeugtes Passwort.
STALWART_RECOVERY_ADMIN=admin:BitteHierEinLangesPasswortSetzen
# Optional: oeffentliche Basis-URL, wenn der Container nicht direkt auf 443 laeuft
# oder ein Reverse Proxy davor steht.
STALWART_PUBLIC_URL=https://mail.example.com
STALWART_RECOVERY_ADMIN erspart Ihnen genau das Problem, das der Testlauf sichtbar gemacht hat: Das beim Erststart generierte Passwort erscheint nur ein einziges Mal im Log. Wer es setzt, hat einen dauerhaft bekannten Notzugang. Setzen Sie die Datei restriktiv, etwa mit chmod 600 .env, und nehmen Sie sie in kein Git-Repository auf.
Installation und erster Start
Verzeichnis anlegen, Dateien ablegen, starten.
# Arbeitsverzeichnis fuer den Stack erstellen und hineinwechseln
mkdir -p /opt/stalwart
cd /opt/stalwart
# Image vorab ziehen, damit der erste Start nicht am Download haengt
docker compose pull
# Stack im Hintergrund starten
docker compose up -d
Direkt danach in die Logs schauen. Das ist kein optionaler Schritt, sondern der einzige Moment, in dem Sie das temporäre Administratorpasswort sehen.
# Logs des Containers anzeigen
docker compose logs stalwart
Im Test erschien dort zuerst die Meldung, dass keine Konfigurationsdatei gefunden wurde und der Server deshalb im Bootstrap-Modus startet, mit Port 8080 für die Ersteinrichtung und der Version 0.16.23. Anschließend stand einmalig ein temporärer Administrator im Log, Benutzername admin plus ein zufällig erzeugtes Passwort, zusammen mit dem Hinweis, dass dieses Passwort nur einmal angezeigt wird und sich per STALWART_RECOVERY_ADMIN=admin:<passwort> fest vorgeben lässt. Die Zeile sah strukturell so aus:
Temporary administrator account created
username = "admin"
password = "<hier-stand-das-zufaellige-passwort>"
note = "This password will only be displayed once."
Notieren Sie dieses Passwort sofort in Ihrem Passwortmanager. Wer das Log verwirft oder den Container neu erstellt, bevor er das Passwort gesichert hat, verliert den Zugang zur Ersteinrichtung.
Ebenfalls im Log des Erststarts: eine Meldung Application resource updated mit der URL zum webui.zip des Projekts. Der Server lädt die Weboberfläche also zur Laufzeit aus dem Internet nach. Ohne Ausgang ins Netz beim Erststart bleibt die Oberfläche leer. Weiter unten stand der Listener mit der Kennung http-recovery auf Port 8080 mit tls = false, also unverschlüsselt. Das ist für die Ersteinrichtung gedacht und kein Zustand, in dem der Port öffentlich erreichbar sein sollte.
Funktions- und Healthcheck
Zuerst der Containerzustand. Im Test erreichte der Container in unter einer Minute Up (healthy), der Healthcheck ist also bereits im Image enthalten und muss nicht in der Compose-Datei ergänzt werden.
# Zustand des Stacks pruefen, Spalte STATUS muss healthy zeigen
docker compose ps
Dann die beiden HTTP-Endpunkte. Beide wurden im Test bestätigt.
# Bereitschaft pruefen, liefert HTTP 200
curl -s -o /dev/null -w '%{http_code}\n' http://localhost:18080/healthz/ready
# Lebendigkeit pruefen, liefert HTTP 200 mit JSON-Koerper
curl -s http://localhost:18080/healthz/live
Die Antwort von /healthz/live lautete im Test:
{"detail":"OK","status":200,"title":"OK","type":"about:blank"}
Diese beiden Endpunkte sind genau das, was Sie in ein externes Monitoring eintragen sollten. /healthz/ready eignet sich als Verfügbarkeitsprüfung, /healthz/live liefert zusätzlich einen auswertbaren JSON-Körper. Ein Monitoring, das nur prüft, ob der Container läuft, erkennt einen hängenden Dienst nicht.
Schließlich der Aufruf der Oberfläche. Ein Request auf den Wurzelpfad antwortete im Test mit HTTP 302 und Location: /account.
# Weiterleitung pruefen, erwartet 302 mit Location /account
curl -s -o /dev/null -D - http://localhost:18080/
Zusätzlich lässt sich im Container gegenprüfen, wer läuft und in welcher Version. Beides wurde im Test bestätigt.
# Prozessbenutzer im Container pruefen, erwartet uid=2000(stalwart)
docker exec stalwart id
# Version des Binaries gegenpruefen, im Test 0.16.23
docker exec stalwart stalwart --version
Erstkonfiguration über die Weboberfläche
Dieser Teil wurde im Testlauf nicht durchgespielt. Der folgende Ablauf entspricht der offiziellen Dokumentation und wird hier bewusst ohne erfundene Klickpfade beschrieben.
Laut Dokumentation läuft Stalwart nach dem ersten Start im Bootstrap-Modus und stellt die Ersteinrichtung über den HTTP-Port bereit. Sie melden sich dort mit dem temporären Konto admin und dem aus dem Log gesicherten Passwort an. Die drei Schritte, die anschließend anstehen:
- Einen dauerhaften Administrator mit eigenem, langem Passwort anlegen und das temporäre Konto anschließend nicht weiterverwenden.
- Die eigene Domäne eintragen, damit Stalwart weiß, für welche Adressen es zuständig ist.
- Das erste Postfach anlegen und damit den späteren Mailempfang vorbereiten.
Die konkreten Menübezeichnungen ändern sich zwischen Versionen. Nehmen Sie die aktuelle offizielle Dokumentation als Referenz, verlinkt am Ende dieser Anleitung. Ob der anschließende Mailversand, der Mailempfang über Port 25 und die IMAP-Anmeldung funktionieren, wurde in diesem Test nicht geprüft und lässt sich deshalb hier nicht bestätigen.
Persistente Daten und Rechte
Das Image deklariert laut Dokumentation zwei Volumes mit klarer Aufgabentrennung:
| Pfad im Container | Inhalt | Verlust bedeutet |
|---|---|---|
/etc/stalwart | Konfigurationsdatei | Neuaufbau der Konfiguration, erneuter Bootstrap |
/var/lib/stalwart | Anwendungsdaten, bei lokalem Backend auch Postfächer | Verlust aller lokal gespeicherten Mail |
Laut Dokumentation wird das Datenvolume nur dann beschrieben, wenn Stalwart ein lokales Storage-Backend wie RocksDB nutzt. Wer ausschließlich externe Speicher verwendet, also S3, PostgreSQL, MySQL, Redis, Azure Blob oder NATS, kann es leer lassen. Ein eigenes Log-Volume ist ebenfalls nicht nötig: Anwendungslogs gehen auf die Standardausgabe des Containers und werden vom Docker-Log-Treiber erfasst. Genau deshalb reicht docker compose logs als Diagnosewerkzeug.
Wichtig ist der Unterschied zwischen benannten Volumes und Bind-Mounts. Der Testlauf hat bestätigt, dass der Dienst im Container als unprivilegierter Benutzer mit UID 2000 läuft. Bei benannten Volumes, wie in den Compose-Dateien oben, setzt Docker die Besitzrechte automatisch passend. Wer stattdessen Hostverzeichnisse einbindet, muss laut Dokumentation selbst dafür sorgen, dass diese Verzeichnisse UID 2000 gehören, sonst kann der Dienst nicht lesen und schreiben.
# Nur bei Bind-Mounts notwendig, vor dem ersten Start ausfuehren
mkdir -p /opt/stalwart/etc /opt/stalwart/data
# Besitzrechte auf den Stalwart-Benutzer im Container setzen
chown -R 2000:2000 /opt/stalwart/etc /opt/stalwart/data
Für die meisten Installationen sind benannte Volumes die weniger fehleranfällige Wahl. Bind-Mounts lohnen sich, wenn Sie die Daten direkt in ein bestehendes Backupziel legen wollen.
Netzwerkfreigabe, Reverse Proxy und TLS
Die Angaben in diesem Abschnitt stammen aus der offiziellen Dokumentation, nicht aus dem Testlauf.
Bemerkenswert und in der Praxis oft übersehen: Laut Dokumentation reicht Dockers Standard-Capability-Set aus, damit Stalwart unter Linux Ports unter 1024 binden kann, weil das Binary die Datei-Capability cap_net_bind_service trägt. Weder --cap-add noch --privileged sind nötig. Umgekehrt gilt: Wer den Container mit --cap-drop NET_BIND_SERVICE startet, bringt das Binary beim Start zum Scheitern. Härtung ohne Prüfung schadet hier also.
Der HTTP-Port der Ersteinrichtung lief im Test ohne TLS, das bestätigt der Listener http-recovery mit tls = false. Eine solche Oberfläche darf nicht offen im Internet stehen. Zwei gangbare Wege:
- Den HTTP-Port nur auf die Loopback-Adresse binden, also
127.0.0.1:8080:8080, und ausschließlich per SSH-Tunnel oder VPN darauf zugreifen. - Einen Reverse Proxy davorsetzen, der TLS terminiert und den Zugriff zusätzlich beschränkt.
Beim Reverse Proxy kommt eine Besonderheit hinzu. Laut Dokumentation muss die öffentliche Basis-URL über die Umgebungsvariable STALWART_PUBLIC_URL durchgereicht werden, wenn das Host-Portmapping nicht 443 ist oder der Proxy den Container unter einer anderen URL bedient. Sonst veröffentlichen die Discovery-Dokumente eine URL, die Clients nicht erreichen.
# Beispiel fuer einen Betrieb hinter einem Proxy auf abweichendem Port
STALWART_PUBLIC_URL=https://mail.example.com:8443
Die Variable akzeptiert eine vollständige Basis-URL mit Schema, Host, optionalem Port und optionalem Pfadpräfix. Was sie ausdrücklich nicht tut: Sie ändert nicht, welche Ports der Container bindet. Das bleibt Sache der ports-Einträge. Für SMTP und IMAP ist ein HTTP-Reverse-Proxy ohnehin kein Werkzeug, diese Ports müssen direkt durchgereicht werden.
DNS-Pflichteinträge
Ohne korrektes DNS ist ein Mailserver funktionslos, unabhängig von der Software. Diese fünf Bausteine brauchen Sie. Die Wirkung in der Zustellung wurde in diesem Test nicht geprüft, die Funktion der Einträge ist aber Standard.
| Eintrag | Wozu |
|---|---|
| MX | Sagt anderen Mailservern, welcher Host Mail für Ihre Domain annimmt. Zeigt auf einen A- oder AAAA-Namen, nie auf eine IP direkt. |
| SPF | TXT-Eintrag, der auflistet, welche Hosts für Ihre Domain senden dürfen. Empfänger verwerfen oder markieren Mail von anderen Absendern. |
| DKIM | TXT-Eintrag mit dem öffentlichen Schlüssel eines Selektors. Der Server signiert ausgehende Mail, Empfänger prüfen die Signatur. Den Schlüssel erzeugt Stalwart, er gehört nicht in eine Anleitung. |
| DMARC | TXT-Eintrag, der festlegt, wie Empfänger mit Mail umgehen sollen, die SPF und DKIM nicht besteht, und wohin Berichte gehen. Beginnen Sie mit einer Beobachtungsrichtlinie, bevor Sie ablehnen lassen. |
| PTR | Reverse-DNS-Eintrag der sendenden IP auf den Hostnamen des Mailservers. Setzt Ihr Anbieter, nicht Sie. Fehlt er oder passt er nicht zum HELO-Namen, lehnen große Empfänger direkt ab. |
Legen Sie diese Einträge an, bevor Sie die erste Mail verschicken. Ein Server, der ohne SPF und PTR sendet, verbrennt die Reputation seiner IP innerhalb von Stunden.
Backup und Restore
Deutlicher Hinweis vorweg: Dieser Abschnitt wurde im Testlauf nicht durchgespielt. Er beschreibt einen konservativen Ansatz, keine verifizierte Prozedur.
Zu sichern sind beide Volumes, stalwart-etc und stalwart-data. Die Konfiguration allein reicht nicht, die Daten allein ebenfalls nicht. Weil ein laufender Mailserver ständig schreibt, sollten Sie den Container vor der Sicherung stoppen, um konsistente Daten zu bekommen. Eine Sicherung im laufenden Betrieb kann einen halb geschriebenen Datenbankzustand erfassen.
# Dienst stoppen, damit keine Schreibvorgaenge laufen
docker compose stop stalwart
# Konfigurationsvolume in ein Archiv sichern
docker run --rm -v stalwart_stalwart-etc:/src:ro -v /backup:/dst alpine \
tar czf /dst/stalwart-etc.tar.gz -C /src .
# Datenvolume in ein Archiv sichern
docker run --rm -v stalwart_stalwart-data:/src:ro -v /backup:/dst alpine \
tar czf /dst/stalwart-data.tar.gz -C /src .
# Dienst wieder starten
docker compose start stalwart
Die Volumenamen tragen das Präfix des Compose-Projekts. Prüfen Sie sie vorher, statt die Namen aus dieser Anleitung zu übernehmen:
# Tatsaechliche Volumenamen des Stacks anzeigen
docker volume ls | grep stalwart
Für die Wiederherstellung gilt die gleiche Vorsicht. Stellen Sie auf demselben oder einem höheren Minor-Stand wieder her, nicht auf einem niedrigeren. Und der wichtigste Punkt: Üben Sie den Restore einmal vollständig in einer Testumgebung, bevor Sie ihn brauchen. Ein Backup, dessen Wiederherstellung nie getestet wurde, ist eine Annahme, kein Backup. Das gilt bei einem Projekt im 0.x-Stand besonders, weil sich Datenformate zwischen Versionen ändern können.
Updates und Rollback-Grenzen
Der folgende Ablauf ist eine Vorsichtsregel und keine im Test verifizierte Prozedur. Ein Update von einer älteren Minor-Version wurde hier nicht durchgeführt.
# Release-Notes vorher lesen, dann neues Image der Minor-Serie ziehen
docker compose pull
# Container mit dem neuen Image neu erstellen
docker compose up -d
# Neue Version im Container gegenpruefen
docker exec stalwart stalwart --version
# Log auf Migrationsmeldungen und Fehler pruefen
docker compose logs --tail 200 stalwart
Warum v0.16 statt latest: Mit latest springt ein docker compose pull irgendwann unbemerkt auf eine neue Minor-Version, möglicherweise mit brechenden Änderungen und einer Datenmigration, die Sie nicht geplant haben. Der Tag v0.16 hält den Stack innerhalb der Serie.
Zur Rollback-Grenze, ehrlich formuliert: Ein Downgrade auf eine ältere Minor-Version ist nicht zugesichert, weil beim Upgrade Datenformate migriert werden können und Migrationen selten rückwärts laufen. Praktisch heißt das, Ihr Rückweg ist das Backup, nicht der alte Image-Tag. Bei einem Projekt im 0.x-Stand mit hoher Änderungsrate gehören deshalb zwei Schritte vor jedes Update: Release-Notes lesen und ein Backup ziehen.
Typische Fehler mit Diagnose und Lösung
DNSSEC-Warnung und deaktiviertes DANE
Dieser Fall trat im Test real auf. Mit dem Standard-Resolver meldete Stalwart, dass der konfigurierte DNS-Resolver DNSSEC nicht validieren kann, dass DANE deshalb deaktiviert wurde, um Mail nicht zu verzögern, und dass der Resolver DNSSEC-fähig und über TCP erreichbar sein sollte.
# Log nach der Resolver-Warnung durchsuchen
docker compose logs stalwart | grep -i dnssec
Die Folge ist keine Störung des Grundbetriebs, aber ein Verlust an Sicherheit: Ohne DANE fällt die Möglichkeit weg, TLS-Zertifikate von Gegenstellen über DNS zu verifizieren. Die Lösung liegt nicht in Stalwart, sondern im Resolver. Verwenden Sie einen DNSSEC-validierenden Resolver, der auch über TCP erreichbar ist, denn DNSSEC-Antworten überschreiten regelmäßig die UDP-Grenze. Prüfen Sie, welchen Resolver der Container tatsächlich benutzt:
# Resolver-Konfiguration im Container anzeigen
docker exec stalwart cat /etc/resolv.conf
Temporäres Administratorpasswort verloren
Das Passwort erscheint nur einmal im Log. Ist der Container inzwischen neu erstellt worden, ist es weg.
# Zuerst pruefen, ob die Zeile noch im Log liegt
docker compose logs stalwart | grep -i -A3 "administrator"
Findet sich nichts mehr, setzen Sie STALWART_RECOVERY_ADMIN=admin:<langes-passwort> in der .env und erstellen den Container neu. Damit haben Sie einen fest definierten Notzugang, statt auf ein flüchtiges Logfenster angewiesen zu sein.
Weboberfläche fehlt oder bleibt leer
Der Server lädt die Oberfläche beim Erststart aus dem Internet nach. Hatte der Host dabei keinen Ausgang, fehlt sie.
# Pruefen, ob der Download der Oberflaeche protokolliert wurde
docker compose logs stalwart | grep -i "Application resource"
# Ausgehende Verbindung aus dem Container heraus testen
docker exec stalwart curl -s -o /dev/null -w '%{http_code}\n' https://github.com
Lösung: Ausgehenden HTTPS-Zugriff für den Erststart freigeben und den Container danach neu starten. In dauerhaft abgeschotteten Netzen planen Sie diesen Schritt ein, bevor Sie den Server in Betrieb nehmen.
Port 25 durch den Hoster gesperrt
Viele Anbieter sperren ausgehenden Verkehr auf Port 25, um Spam zu begrenzen. Der Container startet dann normal, Mail bleibt aber liegen.
# Ausgehende SMTP-Verbindung vom Host aus testen
timeout 10 bash -c 'cat < /dev/tcp/aspmx.l.google.com/25' && echo offen || echo blockiert
Läuft der Test in einen Timeout, ist der Port vermutlich gesperrt. Lösung: Freigabe beim Anbieter beantragen. Bleibt er gesperrt, ist ein Relay über einen Smarthost die Alternative. Ein Mailserver ohne offenen Port 25 kann keine Mail direkt ausliefern.
Rechteproblem bei Bind-Mounts
Ohne passende Besitzrechte kann der Dienst nicht schreiben. Typisch sind Fehler beim Anlegen der Konfiguration direkt nach dem Start.
# Benutzer im Container pruefen, erwartet uid=2000
docker exec stalwart id
# Besitzrechte des Hostverzeichnisses pruefen
ls -ld /opt/stalwart/etc /opt/stalwart/data
Gehören die Verzeichnisse nicht 2000:2000, korrigieren Sie das mit chown -R 2000:2000 und starten den Container neu. Mit benannten Volumes tritt das Problem nicht auf.
Start scheitert nach Capability-Härtung
Wer NET_BIND_SERVICE entfernt, nimmt dem Binary laut Dokumentation die Fähigkeit, Ports unter 1024 zu binden. Der Start scheitert dann.
# Startfehler im Log anzeigen
docker compose logs --tail 50 stalwart
# Aktuellen Zustand pruefen, Restart-Schleifen sind hier sichtbar
docker compose ps
Lösung: cap_drop für NET_BIND_SERVICE entfernen. Zusätzliche Capabilities brauchen Sie nicht, das Standard-Set genügt.
Saubere Deinstallation
Warnung, unwiderruflicher Datenverlust: Der Befehl docker compose down -v --remove-orphans entfernt auch die benannten Volumes stalwart-etc und stalwart-data. Damit sind alle Postfächer, alle Mail und die komplette Konfiguration unwiederbringlich gelöscht. Es gibt keine Rückfrage und keinen Papierkorb. Führen Sie diesen Befehl nur aus, wenn Sie die Daten wirklich nicht mehr brauchen oder ein geprüftes Backup besitzen.
# Nur stoppen und Container entfernen, Daten bleiben erhalten
docker compose down
# Vollstaendig entfernen, LOESCHT auch alle Volumes und damit alle Daten
docker compose down -v --remove-orphans
# Image namentlich entfernen
docker rmi stalwartlabs/stalwart:v0.16
Wer nur vorübergehend abschalten will, nutzt docker compose down ohne -v. Die Volumes bleiben dann bestehen und ein späteres docker compose up -d setzt den Betrieb fort.
Passende Anleitungen auf S-EDV
- Mailcow als Mailserver mit Docker aufsetzen zeigt den Gegenentwurf: ein Verbund mehrerer Container mit Postfix, Dovecot und Rspamd statt eines einzelnen Prozesses.
- Eigener Mailserver mit docker-mailserver beschreibt eine dritte Architektur, die klassische Dienste in einem Image bündelt.
- Reverse DNS und PTR-Einträge für Mailserver einrichten ist die Pflichtlektüre zu dem DNS-Baustein, den Sie nicht selbst setzen können.
Quellen
- stalwartlabs/stalwart auf GitHub, offizielles Repository, Projektstand abgerufen am 24.09.2026.
- Stalwart Docs, Installation mit Docker, Quelle für Tag-Strategie, Volumes, UID 2000, Capabilities und STALWART_PUBLIC_URL.
- Stalwart Release-Notes, Versionsstand v0.16.23 vom 21.09.2026.
- stalwartlabs/stalwart auf Docker Hub, verfügbare Tags und Architekturen.