Watchtower SMTP via Localhost Postfix Relay¶
The Problem¶
Watchtower supports email notifications via its built-in shoutrrr SMTP driver. The typical setup stores SMTP credentials in the compose file or a separate env file. This creates two failure modes:
- Password rotation breaks notifications silently. When you rotate your mail server password, Watchtower keeps running but stops sending emails. You only discover it when you notice container updates happened with no notification.
- Credentials at rest.
docker-compose.ymland.envfiles are often world-readable or checked into git. SMTP passwords stored there are a credential leak waiting to happen.
The shoutrrr SMTP driver also has a quirk: it attempts AUTH over an unencrypted connection to remote SMTP servers, which most mail servers (correctly) reject with 535 5.7.8 authentication failed or similar.
The Solution¶
Route Watchtower's outbound mail through localhost port 25 using network_mode: host. The local Postfix MTA — already running on the host for relay purposes — handles authentication to the upstream mail server. Watchtower never sees a credential.
Watchtower → localhost:25 (Postfix, trusted via mynetworks — no auth required)
→ Postfix → upstream mail server → delivery
docker-compose.yml¶
services:
watchtower:
image: containrrr/watchtower
restart: always
network_mode: host
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- DOCKER_API_VERSION=1.44
- WATCHTOWER_CLEANUP=true
- WATCHTOWER_SCHEDULE=0 0 4 * * *
- WATCHTOWER_INCLUDE_STOPPED=false
- WATCHTOWER_NOTIFICATIONS=email
- WATCHTOWER_NOTIFICATION_EMAIL_FROM=watchtower@yourdomain.com
- WATCHTOWER_NOTIFICATION_EMAIL_TO=you@yourdomain.com
- WATCHTOWER_NOTIFICATION_EMAIL_SERVER=localhost
- WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT=25
- WATCHTOWER_NOTIFICATION_EMAIL_SERVER_TLS_SKIP_VERIFY=true
- WATCHTOWER_NOTIFICATION_EMAIL_DELAY=2
Key settings:
- network_mode: host — required so localhost resolves to the host's loopback interface (and port 25). Without this, localhost resolves to the container's own loopback, which has no Postfix.
- EMAIL_SERVER=localhost, PORT=25 — target the local Postfix
- TLS_SKIP_VERIFY=true — shoutrrr still negotiates STARTTLS even on port 25; a self-signed or expired local Postfix cert is fine to skip
- No EMAIL_SERVER_USER or EMAIL_SERVER_PASSWORD — Postfix trusts 127.0.0.1 via mynetworks, no auth needed
Prerequisites¶
The host needs a Postfix instance that:
1. Listens on localhost:25
2. Includes 127.0.0.0/8 in mynetworks so local processes can relay without authentication
3. Is configured to relay outbound to your actual mail server
This is standard for any host already running a Postfix relay. If Postfix isn't installed, a minimal relay-only config is a few lines in main.cf.
Why Not Just Use an Env File?¶
A separate env file (mode 0600) is better than inline compose, but you still have a credential that breaks on rotation. The localhost relay pattern eliminates the credential entirely.
| Approach | Credentials stored | Rotation-safe |
|---|---|---|
| Inline in compose | Yes (plaintext, often 0644) | ❌ |
| Separate env file (0600) | Yes (protected but present) | ❌ |
| Localhost Postfix relay | None | ✅ |
Testing¶
After docker compose up -d, check the Watchtower logs for a startup notification:
Confirm Postfix delivered it:
Gotchas¶
network_mode: hostis Linux-only. Docker Desktop on macOS/Windows doesn't support host networking. This pattern only works on Linux hosts.network_mode: hostdrops port mappings. Anyports:entries are silently ignored undernetwork_mode: host. Watchtower doesn't expose ports, so this isn't an issue.- Postfix TLS cert warning. shoutrrr attempts STARTTLS on port 25 regardless. If the local Postfix has a self-signed or expired cert,
TLS_SKIP_VERIFY=truesuppresses the error. For a proper fix, renew the Postfix cert. WATCHTOWER_DISABLE_CONTAINERS. If you run stacks that manage their own updates (Nextcloud AIO, etc.), list those containers here (space-separated) to prevent Watchtower from interfering.