One container providing SMTP/IMAP/POP3/JMAP plus CalDAV/CardDAV, an admin
UI and spam filtering — chosen over mailcow (owns its own installer, which
is what killed the earlier attempt now sitting in scripts/unused/) and
over Mailu (~7 containers) because a single image with a single data dir
is the only shape that fits the existing conventions cleanly: one anchor
service the updater can version, one path the backup engine can snapshot.
Mail-specific departures from the usual app template, each deliberate:
* Ports are FIXED, not random. Other mail servers connect to :25 by
number and clients expect 465/587/993 — a randomised external port
would silently make the server unreachable. Only the admin UI takes a
random port, since that one really is just a browser behind Traefik.
143/995/4190/443 ship disabled; the port processor comments them out.
* UPDATE_TYPE=manual and the image pinned to v0.16, not :latest.
Stalwart is pre-1.0 and has said the storage schema is still being
finalised, so an unattended minor bump could carry a data migration on
the message store. This is the one app where the auto default is wrong.
* BACKUP_STRATEGY=stop-snapshot-start. The message store is written
continuously; a live copy can land mid-transaction. Seconds of queued
delivery (senders retry) buys a consistent snapshot.
* The install hook checks outbound port 25 and reverse DNS, then prints
the MX/SPF/DMARC records with real values. A mail server whose
container started is not a working mail server, and every remaining
requirement lives at the registrar or the VPS provider.
Admin credentials are seeded via STALWART_RECOVERY_ADMIN from the app
config rather than left to Stalwart's first-run random password, which
would otherwise exist only in the container log.
Icon is a drawn placeholder, not the upstream trademark.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>