Files
MajorWiki/02-selfhosting/docker/watchtower-smtp-localhost-relay.md
MajorLinux 961ce75b88 Add 4 articles: nginx/apache bad-request jails, SSH fleet hardening, Watchtower localhost relay
All sourced from 2026-04-17 work sessions:
- fail2ban-nginx-bad-request-jail: enable stock jail (just needs wiring)
- fail2ban-apache-bad-request-jail: custom filter from scratch, no stock equivalent
- ssh-hardening-ansible-fleet: drop-in approach with Fedora/Ubuntu edge cases
- watchtower-smtp-localhost-relay: credential-free localhost postfix relay pattern

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 21:06:09 -04:00

4.8 KiB

title, domain, category, tags, status, created, updated
title domain category tags status created updated
Watchtower SMTP via Localhost Postfix Relay selfhosting docker
watchtower
docker
smtp
postfix
email
notifications
published 2026-04-17 2026-04-17

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:

  1. 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.
  2. Credentials at rest. docker-compose.yml and .env files 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:

docker logs <watchtower-container-name> 2>&1 | head -20
# Look for: "Sending notification..."

Confirm Postfix delivered it:

grep watchtower /var/log/mail.log | tail -5
# Look for: status=sent (250 2.0.0 Ok)

Gotchas

  • network_mode: host is Linux-only. Docker Desktop on macOS/Windows doesn't support host networking. This pattern only works on Linux hosts.
  • network_mode: host drops port mappings. Any ports: entries are silently ignored under network_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=true suppresses 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.

See Also