Postfix Relay in Docker installieren
Wenn du ein Postfix Relay Docker Setup suchst, willst du meistens genau eine Sache: Geräte und Dienste aus deinem eigenen Netz sollen Mails verschicken können, ohne dass du in jedem Drucker, Monitoring-Tool oder Script SMTP-Auth-Zugangsdaten hinterlegen musst. Der Relay nimmt die Mail intern an und versendet sie anschließend über deinen richtigen Mailserver weiter. Also über den Server, der SPF, DKIM, DMARC und den ganzen E-Mail-Zirkus sauber beherrscht.
In diesem Beitrag zeige ich dir, wie du einen kleinen Postfix SMTP-Relay in Docker baust. Ich orientiere mich dabei an einem typischen Setup: intern dürfen definierte Netze ohne Auth einliefern, nach außen geht alles über einen richtigen Mailserver auf Port 587 mit STARTTLS und SMTP-Auth. Die Zugangsdaten bleiben natürlich Platzhalter. So viel Abenteuer muss dann doch nicht sein.
🛠 Voraussetzungen
Bevor es losgeht, brauchst du eine laufende Docker-Umgebung. Wenn Docker bei dir noch nicht installiert ist, findest du hier meine passende Schritt-für-Schritt-Anleitung:
Außerdem brauchst du einen Mailserver, über den der Relay später wirklich versenden darf. Im Beispiel nutze ich dafür mail.example.de auf Port 587 mit STARTTLS. Bei dir trägst du dort natürlich deinen eigenen Mailserver ein.
📬 Warum überhaupt ein Postfix Relay?
Ein Relay-Server ist praktisch, wenn mehrere interne Systeme Mails verschicken sollen. Typische Kandidaten sind Monitoring, Backup-Scripts, NAS-Systeme, Drucker, Scanner, Firewalls oder kleine Tools, die zwar E-Mails senden können, aber bei moderner SMTP-Auth eher aussehen, als hätten sie zuletzt 2008 ein Update gesehen.
Statt überall Benutzername, Passwort, Port, TLS-Modus und Absenderadresse zu pflegen, trägst du bei den internen Systemen nur den Relay ein. Der Relay nimmt Mails aus erlaubten Netzen an und kümmert sich zentral um den Versand nach draußen.
- Innen: Geräte aus deinen erlaubten Netzen dürfen Mails ohne Auth an den Relay schicken.
- Außen: Der Relay meldet sich mit SMTP-Auth bei deinem echten Mailserver an.
- Zentral: Du pflegst Zugangsdaten, Absenderregeln und Logs an einer Stelle.
- Kontrolliert: Du erlaubst nur definierte Netze. Sonst baust du aus Versehen ein offenes Relay, und das willst du wirklich nicht.
🧱 Was wir bauen
Wir bauen einen kleinen Docker-Container auf Basis von Ubuntu und Postfix. Die Konfiguration liegt persistent auf dem Host, damit du sie sichern, ändern und nachvollziehen kannst. Der Container bekommt außerdem eine feste IP in einem eigenen Docker-Netzwerk.
Im Beispiel verwende ich diese Werte:
- Container:
postfix-relay - Docker-Netzwerk:
postfix-relay.dockernetwork.local - Container-IP:
172.29.64.10 - SMTP im Container: Port
25 - Optionaler Auth-Listener im Container: Port
2525 - Relayhost nach außen:
[mail.example.de]:587
Das Subnetz kannst du ändern. Wichtig ist nur: Nimm ein privates Netz, das auf deinem Docker-Host noch nicht verwendet wird.
📁 Projektordner vorbereiten
Ich lege Docker-Projekte gerne sauber unter einem eigenen Ordner ab. Für den Relay sieht das so aus:
mkdir -p /home/docker-container/postfix-relay/data/postfix mkdir -p /home/docker-container/postfix-relay/data/spool/postfix mkdir -p /home/docker-container/postfix-relay/data/mail mkdir -p /home/docker-container/postfix-relay/data/log cd /home/docker-container/postfix-relay
Die Ordner sind bewusst getrennt. Postfix-Konfiguration, Queue, lokale Maildaten und Logs liegen damit nicht wild durcheinander. Das freut später auch dein Backup.
🐳 Dockerfile für Postfix
Postfix ist kein typischer Ein-Prozess-Webcontainer. Deshalb bekommt der Container ein kleines Startscript und ein paar Hilfspakete. Wichtig sind hier unter anderem postfix, libsasl2-modules, postfix-pcre, rsyslog, rsync und tini.
FROM ubuntu:22.04
ENV DEBIAN_FRONTEND=noninteractive
ENV TZ=Europe/Berlin
RUN set -eux; \
echo "postfix postfix/mailname string localhost" | debconf-set-selections; \
echo "postfix postfix/main_mailer_type select No configuration" | debconf-set-selections; \
apt-get update; \
apt-get install -y --no-install-recommends \
ca-certificates \
ed \
libsasl2-modules \
mailutils \
netcat-openbsd \
postfix \
postfix-pcre \
rsync \
rsyslog \
sasl2-bin \
tini; \
sed -ri 's/^([[:space:]]*module\(load="imklog"[^)]*\).*)/# \1/' /etc/rsyslog.conf; \
sed -ri 's/^([[:space:]]*\$ModLoad[[:space:]]+imklog.*)/# \1/' /etc/rsyslog.conf; \
mkdir -p /opt/postfix-spool-template /opt/postfix-config-template; \
rsync -a /var/spool/postfix/ /opt/postfix-spool-template/; \
rsync -a /etc/postfix/ /opt/postfix-config-template/; \
rm -rf /var/lib/apt/lists/*
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod 0755 /usr/local/bin/entrypoint.sh
ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/entrypoint.sh"]
CMD ["postfix", "start-fg"]
Der Teil mit /opt/postfix-config-template ist wichtig. Wenn du später /etc/postfix als Volume einhängst, verdeckt Docker die Standarddateien aus dem Image. Das Startscript kann fehlende Dateien dann aus der Template-Kopie nachziehen. Ohne diesen Schritt stolpert man schnell über fehlende Einträge in der master.cf. Einmal erlebt, nie wieder vergessen.
🚪 EntryPoint anlegen
Das Startscript initialisiert die Postfix-Dateien, erzeugt die Hash-Maps mit postmap, startet rsyslog und führt Postfix im Vordergrund aus.
nano entrypoint.sh
#!/bin/sh
set -eu
initialize_spool() {
if [ ! -d /var/spool/postfix/private ] || [ ! -d /var/spool/postfix/public ]; then
rsync -a /opt/postfix-spool-template/ /var/spool/postfix/
fi
}
initialize_config() {
rsync -a --ignore-existing /opt/postfix-config-template/ /etc/postfix/
}
postmap_if_present() {
map_file="$1"
if [ -f "$map_file" ]; then
postmap "$map_file"
fi
}
sync_chroot_resolver() {
mkdir -p /var/spool/postfix/etc
for file in hosts resolv.conf nsswitch.conf services protocols; do
if [ -f "/etc/$file" ]; then
cp -a "/etc/$file" "/var/spool/postfix/etc/$file"
fi
done
}
initialize_config
initialize_spool
sync_chroot_resolver
postfix upgrade-configuration >/dev/null 2>&1 || true
mkdir -p /var/log /var/mail
touch /var/log/mail.log /etc/aliases
if [ -n "${POSTFIX_MYHOSTNAME:-}" ]; then
postconf -e "myhostname = ${POSTFIX_MYHOSTNAME}"
fi
if [ -n "${POSTFIX_RELAYHOST:-}" ]; then
postconf -e "relayhost = ${POSTFIX_RELAYHOST}"
fi
if [ -n "${POSTFIX_MYNETWORKS:-}" ]; then
postconf -e "mynetworks = ${POSTFIX_MYNETWORKS}"
fi
postconf -e "maillog_file = /var/log/mail.log"
if [ -f /etc/postfix/sasl_passwd ]; then
chmod 0600 /etc/postfix/sasl_passwd
fi
if [ -f /etc/sasldb2 ]; then
chown postfix:postfix /etc/sasldb2
chmod 0640 /etc/sasldb2
fi
postmap_if_present /etc/postfix/sasl_passwd
postmap_if_present /etc/postfix/sender_login_maps
postmap_if_present /etc/postfix/transport
newaliases
postfix check
if command -v rsyslogd >/dev/null 2>&1; then
rsyslogd || true
fi
exec "$@"
Danach machst du das Script ausführbar:
chmod +x entrypoint.sh
⚙️ Postfix Relay Docker konfigurieren
Jetzt kommt die eigentliche Postfix-Konfiguration. Lege die Datei data/postfix/main.cf an:
nano data/postfix/main.cf
smtpd_banner = $myhostname ESMTP $mail_name (Docker) biff = no append_dot_mydomain = no compatibility_level = 2 myhostname = postfix-relay.example.lan mydestination = $myhostname, localhost.$mydomain, localhost relayhost = [mail.example.de]:587 mynetworks = 127.0.0.0/8 10.0.1.0/24 172.29.64.0/24 mynetworks_style = subnet inet_protocols = ipv4 recipient_delimiter = + alias_maps = hash:/etc/aliases alias_database = hash:/etc/aliases maillog_file = /var/log/mail.log smtp_use_tls = yes smtp_tls_security_level = encrypt smtp_sasl_auth_enable = yes smtp_sasl_security_options = noanonymous smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd smtp_generic_maps = regexp:/etc/postfix/generic sender_canonical_maps = regexp:/etc/postfix/sender_canonical transport_maps = hash:/etc/postfix/transport smtpd_recipient_restrictions = permit_mynetworks reject_unauth_destination
Der wichtigste Teil ist mynetworks. Dort stehen nur Netze, denen du wirklich vertraust. In meinem Setup sind das zum Beispiel interne Netze und die Docker-Bridge des Relay-Containers. Alles andere wird nicht einfach so weitergeleitet.
Mit relayhost sagst du Postfix, wohin ausgehende Mails gehen sollen. Für mein Setup sieht die relevante Zeile so aus:
relayhost = [mail.example.de]:587
Die eckigen Klammern sind Absicht. Damit verhindert Postfix, dass für den Host noch einmal MX-Records aufgelöst werden. Er soll genau diesen Mailserver verwenden.
SMTP-Auth für den echten Mailserver
Die SMTP-Zugangsdaten für den ausgehenden Mailserver kommen in data/postfix/sasl_passwd:
nano data/postfix/sasl_passwd
[mail.example.de]:587 smtp-benutzer@example.de:DEIN_PASSWORT
Für mein Setup wäre das sinngemäß:
[mail.example.de]:587 relay-user@example.de:DEIN_PASSWORT
Die Datei sollte nur für root lesbar sein:
chmod 600 data/postfix/sasl_passwd
Beim Containerstart macht das EntryPoint-Script daraus automatisch die passende Hash-Datei mit postmap. Wenn du die Datei später im laufenden Betrieb änderst, musst du postmap erneut ausführen oder den Container neu starten.
Absenderadresse vereinheitlichen
Viele interne Systeme senden gerne als root@hostname, monitoring@server oder irgendetwas ähnlich Schönes. Für den Versand nach außen ist das meistens keine gute Idee. Deshalb kannst du lokale Absender auf eine saubere Adresse umschreiben.
nano data/postfix/generic
/^root(@.*)?$/ noreply@example.de /^postfix(@.*)?$/ noreply@example.de
Wenn dein Mailserver nur bestimmte Absender erlaubt, ist das besonders wichtig. Sonst nimmt dein Relay die Mail zwar an, aber der eigentliche Mailserver lehnt sie später ab.
Transport-Map optional vorbereiten
Mit einer Transport-Map kannst du einzelne Ziele anders routen. Für einen einfachen Relay brauchst du das nicht zwingend. Ich lege die Datei trotzdem gerne an, weil Postfix dann beim Start nichts vermisst und spätere Sonderfälle schnell ergänzt sind.
nano data/postfix/transport
# Optional: einzelne Ziele anders routen. # example.net smtp:[smtp.example.net]:25
🧾 Docker Compose Datei
Jetzt kommt der eigentliche Docker-Stack. Jeder Container bekommt eine feste IP. In diesem Beispiel ist es nur ein Container, aber die Struktur bleibt sauber und nachvollziehbar.
---
services:
postfix-relay:
build:
context: .
image: local/postfix-relay:2026-08-26
container_name: postfix-relay
hostname: postfix-relay.example.lan
restart: unless-stopped
env_file:
- .env
ports:
- "25:25/tcp"
volumes:
- ./data/postfix:/etc/postfix
- ./data/aliases:/etc/aliases
- ./data/spool/postfix:/var/spool/postfix
- ./data/mail:/var/mail
- ./data/log:/var/log
healthcheck:
test: ["CMD-SHELL", "postfix status >/dev/null 2>&1 || exit 1"]
interval: 30s
timeout: 5s
retries: 3
start_period: 20s
networks:
postfix_relay_net:
ipv4_address: 172.29.64.10
networks:
postfix_relay_net:
name: postfix-relay.dockernetwork.local
driver: bridge
ipam:
config:
- subnet: 172.29.64.0/24
gateway: 172.29.64.1
ip_range: 172.29.64.128/25
Im Grundsetup mappe ich Host-Port 25 auf Container-Port 25. In produktiven Umgebungen binde ich solche Ports gerne gezielt an die interne Server-IP, zum Beispiel:
ports: - "192.168.1.10:25:25/tcp"
Damit lauscht der Relay nicht blind auf allen Interfaces. Das ist bei Maildiensten eine sehr gesunde Angewohnheit.
Optional: Port 2525 mit SMTP-Auth
Manchmal hast du interne Geräte oder Dienste, die unbedingt SMTP-Auth verwenden wollen. Reolink ist so ein Kandidat. Für solche Fälle kannst du Port 2525 zusätzlich öffnen und dort Auth erzwingen, während Port 25 weiterhin für deine anderen internen Services ohne Auth funktioniert.
Das Prinzip ist dann:
25: interne Clients ausmynetworks, Versand ohne Auth.2525: interne Clients mit Benutzername und Passwort.
Wichtig: Auch Port 2525 gehört nicht offen ins Internet. Wenn ein Gerät darüber Auth macht, ist das trotzdem ein interner Dienst und sollte per Firewall entsprechend begrenzt sein.
Für den Auth-Port brauchst du zusätzlich eine persistente SASL-Datenbank. Lege die Datei vor dem ersten Start an, sonst macht Docker daraus unter Umständen ein Verzeichnis. Das ist dann wieder so ein schöner Moment, in dem man kurz an seinem Beruf zweifelt.
touch data/sasldb2 chmod 640 data/sasldb2
Dann ergänzt du im Compose-File den zweiten Port und die SASL-Datenbank:
ports: - "192.168.1.10:25:25/tcp" - "192.168.1.10:2525:2525/tcp" volumes: - ./data/postfix:/etc/postfix - ./data/aliases:/etc/aliases - ./data/sasldb2:/etc/sasldb2 - ./data/spool/postfix:/var/spool/postfix - ./data/mail:/var/mail - ./data/log:/var/log
Damit Postfix Benutzer aus der SASL-Datenbank prüfen kann, legst du die Datei data/postfix/sasl/smtpd.conf an:
mkdir -p data/postfix/sasl nano data/postfix/sasl/smtpd.conf
pwcheck_method: auxprop auxprop_plugin: sasldb mech_list: PLAIN LOGIN
Danach brauchst du eine Zuordnung, welche Absenderadresse zu welchem Login passt:
nano data/postfix/sender_login_maps
relay@example.de smtpauth
Den zusätzlichen Listener trägst du in data/postfix/master.cf ein. Die Datei wird beim ersten Containerstart aus dem Image nach data/postfix kopiert. Starte den Container also einmal, stoppe ihn wieder und ergänze dann am Ende der Datei diesen Block:
2525 inet n - n - - smtpd -o smtpd_sasl_auth_enable=yes -o smtpd_sasl_type=cyrus -o smtpd_sasl_path=smtpd -o smtpd_tls_security_level=none -o smtpd_tls_auth_only=no -o smtpd_sender_login_maps=hash:/etc/postfix/sender_login_maps -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject_unauth_destination,reject_sender_login_mismatch -o smtpd_helo_required=yes -o disable_vrfy_command=yes
Jetzt startest du den Container und legst den Benutzer an:
docker compose up -d --build docker exec -it postfix-relay saslpasswd2 -c -u postfix-relay.example.lan smtpauth docker exec postfix-relay postmap /etc/postfix/sender_login_maps docker exec postfix-relay postfix reload
Im Client verwendest du dann als SMTP-Server deinen Relay, als Port 2525, als Benutzer smtpauth und das Passwort, das du mit saslpasswd2 vergeben hast. Der Client authentifiziert sich also am Relay. Der Relay selbst schickt die Mail danach weiterhin über deinen richtigen Mailserver raus.
Umgebungsvariablen
Die .env ist optional, aber praktisch. Darüber kannst du Hostname, Relayhost und erlaubte Netze anpassen, ohne direkt die main.cf zu bearbeiten.
POSTFIX_MYHOSTNAME=postfix-relay.example.lan POSTFIX_RELAYHOST=[mail.example.de]:587 POSTFIX_MYNETWORKS=127.0.0.0/8 10.0.1.0/24 172.29.64.0/24
🚀 Stack starten
Wenn alle Dateien liegen, startest du den Stack:
docker compose up -d --build docker compose ps
Auf meinem Testsystem sah das anschließend so aus:
NAME IMAGE STATUS PORTS postfix-relay local/postfix-relay:2026-08-26 Up 45 seconds (healthy) 0.0.0.0:25->25/tcp
Die feste Container-IP kannst du ebenfalls prüfen:
docker inspect postfix-relay --format 'IP={{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'
IP=172.29.64.10
🧪 Relay testen
Als erstes prüfst du, ob Postfix überhaupt antwortet:
printf quit | nc -w 3 127.0.0.1 25
220 postfix-relay.example.lan ESMTP Postfix (Docker)
Danach kannst du eine Testmail einliefern. Das testet erst einmal die Annahme durch den Relay. Ob der echte Versand nach außen klappt, hängt anschließend von Relayhost, Zugangsdaten, Absenderadresse und DNS ab.
{
printf 'EHLO server.local\r\n'
printf 'MAIL FROM:<monitoring@example.lan>\r\n'
printf 'RCPT TO:<admin@example.net>\r\n'
printf 'DATA\r\n'
printf 'Subject: Postfix Relay Test\r\n\r\nDas ist ein lokaler Test.\r\n.\r\n'
printf 'QUIT\r\n'
} | nc -w 5 127.0.0.1 25
Bei mir wurde die Mail sauber angenommen:
250 2.1.0 Ok 250 2.1.5 Ok 354 End data with <CR><LF>.<CR><LF> 250 2.0.0 Ok: queued as 9EE528BD30 221 2.0.0 Bye
Die Queue prüfst du so:
docker exec postfix-relay postqueue -p
Wenn alles raus ist, sieht es so aus:
Mail queue is empty
🔐 Kein offenes Relay bauen
Der wichtigste Sicherheitspunkt ist simpel: Öffne diesen Relay nicht für das Internet. Ein SMTP-Relay, das von überall ohne Auth annimmt, wird früher oder später missbraucht. Wahrscheinlich früher.
Achte besonders auf diese Punkte:
mynetworksenthält nur deine internen Netze und wirklich notwendige Einzel-IP-Adressen.- Der Docker-Port ist möglichst nur an eine interne IP gebunden.
- Die Firewall erlaubt Port
25oder2525nur aus deinen internen Netzen. - Der Relayhost nach außen nutzt STARTTLS und SMTP-Auth.
- Absenderadressen passen zu deiner Domain und sind auf dem Mailserver erlaubt.
Wenn du unsicher bist, teste von einem fremden Netz aus, ob dein Relay ablehnt. Von außen sollte ohne Auth nichts relayen können.
🧯 Typische Fehler
Relay access denied
Dann kommt der Client sehr wahrscheinlich nicht aus einem Netz, das in mynetworks erlaubt ist. Prüfe die Quell-IP aus Sicht des Containers und passe die Netze nur dann an, wenn die Quelle wirklich vertrauenswürdig ist.
Authentication failed beim Versand
Dann stimmen Benutzername, Passwort oder der Eintrag in sasl_passwd nicht. Wichtig: Der Host in sasl_passwd muss exakt zum relayhost passen, also inklusive eckiger Klammern und Port.
relayhost = [mail.example.de]:587 # sasl_passwd [mail.example.de]:587 smtp-benutzer@example.de:DEIN_PASSWORT
Änderungen greifen nicht
Wenn du Maps wie sasl_passwd oder transport änderst, musst du die Hash-Dateien neu erzeugen und Postfix neu laden:
docker exec postfix-relay postmap /etc/postfix/sasl_passwd docker exec postfix-relay postmap /etc/postfix/transport docker exec postfix-relay postfix reload
Oder du startest den Container neu. Das ist nicht elegant, aber ehrlich gesagt bei einem kleinen Relay oft völlig ausreichend.
✅ Fazit
Ein Postfix Relay in Docker ist kein Hexenwerk, aber du solltest sauber trennen: intern dürfen nur definierte Netze einliefern, extern versendet dein Relay über den richtigen Mailserver mit Auth und TLS. Genau dadurch bleibt das Setup bequem für interne Geräte, ohne dass du dir ein offenes Relay ins Netz stellst.
Ich mag diese Lösung besonders für Monitoring, Benachrichtigungen und ältere Geräte. Du hast zentrale Logs, eine zentrale Absenderlogik und musst SMTP-Zugangsdaten nicht überall verteilen. Das ist im Alltag angenehm unspektakulär. Und bei Infrastruktur ist angenehm unspektakulär meistens ein sehr gutes Zeichen.
👥 Techniverse Community
Lust auf Austausch rund um Docker, Selfhosting und andere smarte IT-Lösungen?
In der Techniverse Community triffst du Gleichgesinnte, kannst Fragen stellen oder einfach nerdigen Talk genießen.
👉 Jetzt der Gruppe auf Matrix beitreten
~ Direkte Raumadresse: #community:techniverse.net
👉 Für lockere Gespräche abseits der Kernthemen komm in den Talkraum
~ Direkte Raumadresse: #talk:techniverse.net
Wir freuen uns, wenn du dabei bist!



