Ein E-Mail-Abruf, der von einem laufenden Outlook abhängt, ist im Alltag schnell eingerichtet. Für einen dauerhaften Betrieb entstehen daraus jedoch zusätzliche Abhängigkeiten: Der Arbeitsplatz muss verfügbar sein, Outlook muss laufen und die richtige Senden-/Empfangen-Gruppe muss das Konto tatsächlich berücksichtigen.
Wir haben diese Aufgabe auf einen bestehenden Rocky-Linux-Server verlagert. Dort holen jetzt zwei zusammenarbeitende Container die Nachrichten eines externen POP3-Postfachs ab und geben sie an ein zentrales Zielpostfach weiter. Fetchmail übernimmt die Abholung, Postfix die dauerhafte Warteschlange und die weitere SMTP-Zustellung.
Der Weg dorthin bestand aus mehr als einer Compose-Datei. Wir haben den bisherigen Mailfluss aufgeräumt, Quellen und Images geprüft, die Firewall gezielt ergänzt, TLS auf beiden Transportstrecken getestet und den Wechsel mit einzelnen Nachrichten nachvollzogen. Zum Schluss musste auch eine wartende Nachricht die Neuerstellung ihres Containers überstehen.
Alle Adressen, Namen und frei gewählten Dateipfade in den Beispielen sind neutralisiert. Die Ausschnitte erläutern unseren Aufbau; sie bilden kein vollständiges Installationsskript. Versionsangaben beschreiben den geprüften Stand vom Oktober 2026.
Zuerst klären, welche Postfächer überhaupt POP3 brauchen
Die ursprüngliche Konfiguration enthielt mehrere POP-Konten. Bei der Bestandsaufnahme zeigte sich jedoch, dass ein Teil der Nachrichten bereits auf einem eigenen Mailgateway ankam. Für diese Konten konnten wir die Weitergabe direkt auf dem Server einrichten. Eine zusätzliche Abholung durch Outlook war dafür nicht mehr erforderlich.
Auf dem vorhandenen MailEnable-Gateway bereinigten wir lokale Adresszuordnungen und Weiterleitungen. Dabei musste klar sein, welche Adresse lokal zu einer Mailbox gehört und welche über SMTP an das Ziel weitergehen soll. Eine Weiterleitung auf eine Adresse, die wieder auf dieselbe lokale Mailbox zeigt, wäre keine brauchbare Übergabe.
Ein Fehler machte diese Unterscheidung sichtbar: Eine Testnachricht wurde bereits beim SMTP-Empfängerkommando mit einem 550-Fehler zurückgewiesen. Im Debugprotokoll stand, dass die Domain lokal bedient werde, der Empfänger aber nicht in der Adresstabelle definiert sei. Die vorgesehenen Domain-Routingeinträge waren nicht mehr gespeichert. Wir trugen sie erneut ein und kontrollierten danach sowohl die gespeicherte Konfiguration als auch die tatsächliche Zustellung.
Zur Bereinigung gehörte auch die Wartung des vorhandenen Mailgateways. Nach Sicherung und Aktualisierung prüften wir Dienste und mehrere Empfängerpfade mit neuen Nachrichten. Anwendung und Mailfluss brauchen dabei jeweils einen eigenen Funktionsnachweis.
Erst als diese serverseitigen Wege funktionierten, entfernten wir die überflüssigen POP-Definitionen aus Outlook. Für den neuen Abholer blieb genau ein externes Providerkonto übrig. Diese Eingrenzung hat die spätere Konfiguration deutlich übersichtlicher gemacht.
Zwei Container mit unterschiedlichen Aufgaben
Für das externe Konto entschieden wir uns für Fetchmail in Docker. Der vorhandene Rocky-Linux-Host hatte genügend Arbeitsspeicher und freien Platz auf dem Docker-Datenträger. Ein zusätzlicher Server war für diese kleine Aufgabe nicht erforderlich.
Fetchmail holt die Nachrichten beim Provider ab. Postfix nimmt sie lokal per SMTP an, speichert sie in seiner Queue und stellt sie an den festgelegten SMTP-Endpunkt weiter zu. Der Aufbau sieht vereinfacht so aus:
Externes Providerpostfach
|
| POP3S, TCP 995, Zertifikatspruefung
v
Fetchmail-Container
|
| SMTP ueber 127.0.0.1 im gemeinsamen Netzwerk-Namespace
v
Postfix-Container mit persistenter Queue
|
| SMTP mit verpflichtendem STARTTLS und Zertifikatspruefung
v
Zentrales Zielpostfach
Die Queue war eine bewusste Anforderung. Wenn das Ziel vorübergehend nicht erreichbar ist, soll eine bereits angenommene Nachricht lokal auf ihre spätere Zustellung warten können. Damit erhält die Grenze zwischen Abholung und weiterer Zustellung eine konkrete Bedeutung: Nach der positiven Annahme durch Postfix liegt die Verantwortung bei unserer eigenen Warteschlange.
Fetchmail und Postfix haben wir dafür nicht als zusätzliche Systemdienste auf Rocky installiert. Beide dauerhaften Anwendungsprozesse laufen in Containern. Die Python-Skripte auf dem Host dienten der Einrichtung und Prüfung und waren danach beendet. Docker selbst, das Dateisystem und die Netzwerkkonfiguration bleiben natürlich Aufgaben des Hosts.
Den ausgehenden Netzwerkweg gezielt öffnen
Der Docker-Host war bereits durch ausgehende Firewallregeln eingeschränkt. Vor der Einrichtung kontrollierten wir deshalb den tatsächlichen Verbindungsweg zum Provider. Eine vorhandene Freigabe für Webzugriffe deckt POP3S auf Port 995 nicht mit ab.
Wir ergänzten eine gezielte Regel für den Docker-Host, den Provider-FQDN und TCP 995. Die Regel steht vor der allgemeinen ausgehenden Sperre. Die übrigen Sperren blieben bestehen. Für die Einrichtung brauchte es keine allgemeine Internetfreigabe des Hosts.
Ein FQDN als Ziel verhindert zudem, dass eine bei einem einzelnen Test aufgelöste Provider-IP ungeprüft zur dauerhaften Vorgabe wird. Wie die Firewall solche Objekte aktualisiert, gehört weiterhin zu ihrer eigenen Konfiguration.
Den ersten Test führten wir vom Rocky-Host aus. Später wiederholten wir ihn aus dem tatsächlichen Containerimage. Beide Prüfungen waren nötig, weil Host und Container unterschiedliche Resolver- und Netzwerkbedingungen haben können.
timeout 20 openssl s_client \
-connect pop.provider.example:995 \
-servername pop.provider.example \
-verify_hostname pop.provider.example \
-verify_return_error \
-brief </dev/null
Wir prüften dabei die Zertifikatskette und den erwarteten Servernamen. Der Verbindungsaufbau allein hätte für diese Abnahme nicht genügt. In unserem Test war POP3S mit TLS 1.3 erfolgreich; die Prüfung meldete sowohl ein gültiges Zertifikat als auch den erwarteten Gegenstellennamen.
Ein eigenes Fetchmail-Image aus geprüfter Quelle
Wir verwendeten Fetchmail 6.6.9 und bauten dafür ein eigenes Image auf Debian 13 slim. Die konkrete Version sollte im Build und später in der Laufzeit nachvollziehbar bleiben. Die Fetchmail-Projektseite nennt die Releases und die zugehörigen Sicherheitskorrekturen. Die Auswahl einer Version ersetzt deshalb keinen späteren Updateprozess.
Zuerst zogen wir das offizielle Debian-Basisimage und hielten dessen Digest fest. Danach luden wir das offizielle Fetchmail-Quellarchiv und die abgetrennte OpenPGP-Signatur herunter. Ein SHA256-Wert dokumentierte die erhaltene Archivdatei.
Dieser Hash war zunächst nur eine Referenz auf unseren Download. Für die Herkunftsprüfung kontrollierten wir zusätzlich die Signatur. Den erwarteten Signierfingerprint glichen wir mit einer über HTTPS veröffentlichten Projektwarnung ab und prüften sowohl diese Warnung als auch das Archiv mit demselben erwarteten Schlüssel.
Die GnuPG-Prüfung lief in einem kurzlebigen Container mit eigenem Schlüsselbund. Dadurch mussten wir keinen dauerhaften GnuPG-Schlüsselbund des Hostadministrators verändern. Die Meldung über eine gültige Signatur erschien zusammen mit dem Hinweis, dass der Schlüssel im frischen Schlüsselbund nicht über ein persönliches Vertrauensnetz zertifiziert sei. Unsere Zuordnung stützte sich auf die über HTTPS bezogenen Projektangaben; ein eigenes Web of Trust hatten wir damit nicht aufgebaut.
Buildumgebung und Laufzeit voneinander trennen
Das Dockerfile erhielt zwei Stufen. In der Buildstufe lagen Compiler, Entwicklungsbibliotheken, Prüfwerkzeuge und der Quellcode. Dort prüften wir Archivhash und Signaturen erneut, konfigurierten OpenSSL-Unterstützung und schalteten die nicht benötigte NTLM-Unterstützung ausdrücklich ab.
Nach der Übersetzung führten wir die mitgelieferten Tests aus. Der Lauf meldete fünf bestandene und zwei übersprungene Tests sowie keinen fehlgeschlagenen Test. Die übersprungenen Tests haben wir als solche dokumentiert. Sie waren keine zusätzlich bestandenen Prüfungen.
Das Laufzeitimage erhielt die installierte Anwendung und ihre benötigten Laufzeitpakete. Compiler und GnuPG wurden aus der Buildstufe nicht übernommen. Eine Bibliotheksprüfung kontrollierte, dass keine dynamische Abhängigkeit fehlte. Fetchmail läuft unter einem eigenen Benutzer ohne Rootrechte; Tini übernimmt die Rolle des kleinen Init-Prozesses im Container.
Auch die abschliessende Versionsprüfung führten wir mit diesem Laufzeitimage aus. Dabei gab es zwei Fehler in unserem Prüfskript: Zuerst war die lange Option für die Konfigurationsdatei falsch geschrieben. Fetchmail erwartet --fetchmailrc. Danach suchte die Prüfung nach der älteren Versionskennzeichnung +SSL, während unser Build 6.6.9+TLS ausgab.
Der Compilerlauf war zu diesem Zeitpunkt bereits erfolgreich. Wir korrigierten die Prüfungen und nutzten die vorhandenen Buildschichten weiter. Die abschliessende Versionsprüfung bestätigte Fetchmail 6.6.9 mit TLS-Unterstützung und der zugehörigen Laufzeitbibliothek.
Die Images legten wir anschliessend über ihre geprüften IDs fest. Das schützt vor einem unbeabsichtigten Austausch über einen veränderlichen Tag. Es bedeutet zugleich, dass eine Sicherheitsaktualisierung einen bewusst geprüften Neubau und Austausch braucht. Festgelegte Images aktualisieren sich nicht von selbst.
Zwei Container teilen sich einen lokalen SMTP-Zugang
Für die Übergabe an Postfix verwenden wir einen gemeinsamen Netzwerk-Namespace. Fetchmail nutzt in Compose den Netzwerkmodus des Postfix-Dienstes. Dadurch erreicht es dessen SMTP-Listener über 127.0.0.1:25. Postfix lauscht ausschliesslich auf Loopback; am Docker-Host wird kein SMTP-Port für diese Anwendung veröffentlicht.
Ein gekürzter Compose-Ausschnitt zeigt diesen Teil des Aufbaus. Die lokalen Imagenamen sind Beispiele; Mounts, Startbedingungen und weitere Einstellungen gehören in die vollständige Konfiguration.
services:
postfix:
image: local/mailqueue:geprueft
restart: unless-stopped
volumes:
- queue:/var/spool/postfix
- data:/var/lib/postfix
fetchmail:
image: local/fetchmail:6.6.9
network_mode: service:postfix
read_only: true
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
restart: unless-stopped
volumes:
queue:
data:
Die Compose-Dokumentation zu Diensten beschreibt Netzwerkmodus, Startbedingungen und Ressourcenbegrenzungen. In unserer Laufzeitkonfiguration ergänzten wir einen schreibbaren Statusbereich, ein kleines temporäres Dateisystem, begrenzte CPU- und Speicherressourcen sowie rotierende Containerlogs.
Diese enge Kopplung hat eine Betriebsfolge: Wenn wir den Postfix-Container neu erstellen, müssen wir auch Fetchmail passend neu erstellen, damit es den richtigen Netzwerk-Namespace nutzt. Eine solche Wartung behandeln wir deshalb gemeinsam für beide Dienste.
TLS auf der weiterführenden SMTP-Strecke erzwingen
Zwischen Fetchmail und Postfix bleibt SMTP innerhalb des gemeinsamen Netzwerk-Namespace. Für die anschliessende Netzwerkverbindung zum festgelegten Mailziel konfigurierten wir verpflichtendes TLS mit Prüfung der Zertifikatskette und des Zielnamens.
Der entsprechende Ausschnitt aus der Postfix-Konfiguration sieht mit neutralen Namen so aus:
relayhost = [smtp.intern.example]:25
smtp_tls_security_level = secure
smtp_tls_secure_cert_match = nexthop
smtp_tls_CAfile = /etc/postfix/beispiel-ca.pem
smtp_tls_mandatory_protocols = >=TLSv1.2
smtp_tls_loglevel = 1
Die Klammern legen den Relayhost ohne MX-Abfrage fest. Für die Zertifikatsprüfung ist der konfigurierte Name des nächsten Ziels massgeblich. Fehlt eine passende geprüfte TLS-Verbindung, soll Postfix die Nachricht zurückstellen. Die Postfix-Parameterdokumentation erläutert die Sicherheitsstufe secure und den nexthop-Abgleich.
Wir testeten beide vorgesehenen SMTP-Ziele zuerst vom Host und danach aus dem Container. Für die interne Zertifikatskette genügte eine Kopie des öffentlichen CA-Zertifikats. Ein privater Schlüssel des Zielservers musste dafür nicht übertragen werden.
Auf Rocky blieb SELinux aktiv. Die CA-Datei legten wir in einem eigenen Anwendungsverzeichnis ab und banden sie mit passendem Label schreibgeschützt ein. Eine bereits von einer anderen Anwendung genutzte Zertifikatsdatei haben wir dafür nicht umgelabelt.
Zugangsdaten und Zustand bleiben ausserhalb des Images
Das Providerkennwort gehört weder in das Dockerfile noch in einen Buildparameter oder in eine öffentlich lesbare Compose-Datei. Wir fragten es während der Einrichtung verdeckt im Terminal ab und schrieben es in eine geschützte Fetchmail-Konfigurationsdatei auf dem Host.
Die Datei ist nur für den vorgesehenen Laufzeitbenutzer lesbar und wird schreibgeschützt in den Container eingebunden. Das übergeordnete Hostverzeichnis gehört dem Administrator und ist entsprechend eingeschränkt. Diese Datei bleibt ein gespeichertes Geheimnis; der Betrieb in Docker verschlüsselt sie nicht automatisch.
Getrennt davon liegt die persistente UIDL-Datei. Sie hält fest, welche Provider-Nachrichten Fetchmail bereits gesehen hat. Die benötigten Schreibrechte bekommt nur dieser Statusbereich. Das übrige Fetchmail-Dateisystem bleibt schreibgeschützt.
Postfix speichert seine Queue und seine weiteren persistenten Daten in benannten Docker-Volumes. Die Docker-Dokumentation zu Volumes beschreibt deren vom Container getrennten Lebenszyklus. Ein neu erstellter Container kann dieselben Volumes wieder verwenden. Ein gelöschtes Volume wäre dagegen ein Verlust dieses Zustands.
SMTP-Annahme mit erlaubten und abgewiesenen Empfängern testen
Die neue Queue sollte genau einem Zielpostfach dienen. Wir haben deshalb zuerst eine künstliche Nachricht lokal in Postfix eingespeist und die weitere Zustellung geprüft. Anschliessend testeten wir den späteren SMTP-Zugang aus dem Fetchmail-Image mit dessen unprivilegiertem Benutzer.
Die Probe bestand aus drei Empfängerfällen. Eine andere lokale Adresse musste abgewiesen werden. Eine externe Adresse musste an der Relaygrenze scheitern. Nur die vorgesehene Zieladresse durfte bis zur Nachrichtenannahme gelangen. Bei den beiden negativen Proben sendeten wir keine Nachrichtendaten.
Die Ablehnungen waren ebenso Teil der Abnahme wie der erfolgreiche Fall. Ein Listener auf Loopback begrenzt bereits den Zugang. Die Empfängerprüfung begrenzt zusätzlich, wofür dieser Zugang verwendet werden darf.
Auch die Fehlerbehandlung gehört in die Abnahme. Eine temporäre Ablehnung mit 4xx und eine dauerhafte Ablehnung mit 5xx haben unterschiedliche Folgen für Wiederholungen und Benachrichtigung. Eine bewusst gewählte Pilotkonfiguration muss deshalb zusammen mit dem späteren Überwachungs- und Benachrichtigungskonzept bewertet werden.
Erst anmelden, dann eine einzige Nachricht abholen
Vor dem tatsächlichen Abruf prüften wir mit Fetchmail zunächst nur die Anmeldung. Der Checkmodus meldete eine erfolgreiche Verbindung ohne abzuholende Nachrichten. Das passte zum bisherigen Betrieb: Outlook hatte ältere Nachrichten abgeholt und beim Provider gelöscht. Ein Altbestand musste nicht importiert werden.
Danach schalteten wir den bisherigen POP-Abruf in Outlook aus. Erst anschliessend versendeten wir eine neue Testnachricht und starteten einen begrenzten Einzelabruf. Für diesen ersten Lauf blieben Providerkopien ausdrücklich erhalten.
Der zentrale Teil des Aufrufs war:
fetchmail \
--nodetach --nosyslog --daemon 0 --fetchlimit 1 \
--fetchmailrc /etc/fetchmail/fetchmailrc \
--idfile /var/lib/fetchmail/fetchids \
--pidfile /tmp/fetchmail.pid
Der Befehl lief innerhalb eines temporären Containers mit den vorbereiteten Mounts und dem passenden Netzwerk-Namespace. Die Fetchmail-Anleitung beschreibt Checkmodus, Abrufbegrenzung und Konfigurationsoptionen. Der Einzelabruf in unserem Pilot las eine Nachricht und meldete not flushed. Das entsprach der eingestellten Aufbewahrung auf dem Provider.
Die nächste Prüfung erfolgte am Ziel. Eine positive SMTP-Antwort belegt zunächst die Annahme zur weiteren Verarbeitung. Wir kontrollierten zusätzlich das Zustellprotokoll des Zielsystems und bestätigten die Ablage im vorgesehenen Postfach.
UIDL muss eine Container-Neuerstellung überstehen
Bei POP3 dürfen laufende Nachrichtennummern nicht mit einer dauerhaften Nachrichtenidentität verwechselt werden. Für die Wiedererkennung verwendet unser Aufbau UIDL-Kennungen und den dazu gespeicherten Fetchmail-Status. Das POP3-Protokoll beschreibt UIDL in RFC 1939.
Wir prüften das mit der absichtlich erhaltenen Providerkopie. Nach dem ersten erfolgreichen Abruf beendeten wir den temporären Container. Ein neuer Container bekam dieselbe persistente UIDL-Datei und führte denselben begrenzten Lauf aus. Es wurde keine zusätzliche Nachricht versendet.
Die Ausgabe meldete nun eine Nachricht mit dem Zusatz 1 seen. Es erschien keine erneute Leseoperation. Damit war für unseren geprüften Fall nachgewiesen, dass der Status ausserhalb des ersten Containers lag und beim nächsten Lauf wieder verwendet wurde.
Eine allgemeine Garantie für genau eine Zustellung ergibt sich daraus nicht. Verbindungsabbrüche und unklare Bestätigungen können bei Mailtransporten weiterhin Sonderfälle erzeugen. Der Test belegt konkret, dass ein normaler Containerwechsel die bereits bekannte Nachricht in diesem Aufbau nicht erneut importierte.
Die Queue mit einer tatsächlich wartenden Nachricht prüfen
Ein laufender Postfix-Container und ein vorhandener Volumename waren uns als Nachweis für die Persistenz noch zu wenig. Wir wollten eine Nachricht sehen, die vor der Neuerstellung wartete und danach weiterhin vorhanden war.
Vor diesem Test sicherten wir die Konfiguration, den UIDL-Status und beide Postfix-Volumes. Für eine konsistente Kopie der Queue stoppten wir Postfix kurz. Danach prüften wir die Archivlisten und erzeugten Prüfsummen. Die Sicherung enthält Zugangsdaten und bleibt in einem geschützten Bereich.
Während der Sicherung meldete tar, dass Unix-Sockets ignoriert würden. Diese Sockets gehören zur lokalen Kommunikation laufender Postfix-Prozesse und werden beim Start neu angelegt. Entscheidend waren die gespeicherten Nachrichten und der übrige benötigte Zustand.
Für den eigentlichen Test erzeugten wir eine künstliche Nachricht mit einem eindeutigen Prüfheader. Eine vorübergehende, auf diesen Header begrenzte Regel legte sie in die Hold-Queue. Die Postfix-Dokumentation zu Headerprüfungen beschreibt diese HOLD-Aktion.
Wir erfassten die Queue-ID, erstellten den Postfix-Container mit seinen vorhandenen Volumes neu und fanden anschliessend dieselbe Nachricht mit derselben ID wieder. Danach entfernten wir die Prüfregel, gaben ausschliesslich diese Testnachricht frei und bestätigten ihre erfolgreiche SMTP-Weitergabe.
Damit war der Wiederanlauf einer echten wartenden Queue-Nachricht geprüft. Ein vollständiger Restore aus einem Sicherungsarchiv ist davon zu unterscheiden: Er muss auch das Wiederherstellen der gesicherten Daten und den anschliessenden Anwendungsstart umfassen.
Vom sicheren Pilot zum automatischen Abruf
Nach den erfolgreichen Tests legten wir die spätere Provideraufbewahrung fest. Wie zuvor bei Outlook sollen neu abgeholte Nachrichten nach erfolgreicher Übergabe an die lokale Postfix-Queue beim Provider gelöscht werden. Wir stellten deshalb von keep auf nokeep um.
Der Löschzeitpunkt liegt vor der endgültigen Ablage im Zielpostfach: Fetchmail hat die Nachricht an Postfix übergeben, und Postfix hat die SMTP-Annahme bestätigt. Eine spätere Unterbrechung zum Ziel muss nun die persistente Queue auffangen. Genau deshalb gehörten deren Sicherung und Wiederanlaufprüfung zur Umstellung.
Für den Dauerbetrieb entfernten wir das manuelle Profil, aktivierten die Neustartrichtlinie unless-stopped und starteten Fetchmail als Vordergrundprozess mit einem Intervall von 60 Sekunden. Die vorhandene UIDL-Datei blieb erhalten. Wir verwendeten keine pauschalen Optionen zum erneuten Vollabruf oder zum Löschen älterer Serverkopien.
Die letzte Testnachricht musste anschliessend ohne manuellen Abruf ankommen. In den Logs erschien der reguläre Poll, danach das Lesen der neuen Nachricht mit flushed. Postfix bestätigte die Weitergabe über verifiziertes TLS, und das Zielsystem protokollierte die Postfachzustellung. Die frühere Pilotkopie wurde weiterhin als bereits gesehen erkannt.
Damit funktionierten automatische Abholung, Providerlöschung und tatsächliche Zustellung gemeinsam. Outlook war an diesem Ablauf nicht mehr beteiligt.
Was nach der Umstellung zum Betrieb gehört
Die automatische Abholung und Zustellung wurden im beschriebenen Test erfolgreich abgeschlossen. Zu einem dauerhaften Abholdienst gehören geschützte Sicherungen von Konfiguration und Zustand, Wiederherstellungstests sowie die Überwachung von Anmeldung, Abrufintervall, Queuealter, Queuegrösse und freiem Speicherplatz.
Eine Neustartrichtlinie kann einen beendeten Container wieder starten. Ob das Providerkonto erreichbar ist und Nachrichten innerhalb der erwarteten Zeit ankommen, muss gesondert sichtbar werden. Dauerhafte Fehler brauchen eine passende Benachrichtigung; eine wartende Nachricht darf nicht allein durch einen grünen Containerstatus unbemerkt bleiben.
Für mich war die wichtigste praktische Erkenntnis dieser Umstellung die klare Übergabe zwischen den Komponenten. Fetchmail darf erst nach bestätigter Queue-Annahme löschen. Die Queue braucht ihren eigenen persistenten Zustand. Und die Abnahme endet erst dann, wenn eine neue Nachricht automatisch im richtigen Postfach angekommen ist.