Deploying / Stalwart
Stalwart
Installing the Stalwart template -- SMTP, IMAP, JMAP, CalDAV and CardDAV in one server -- from first boot to sending mail.
Stalwart is a mail and collaboration server: SMTP, IMAP, POP3, JMAP, ManageSieve, CalDAV and CardDAV in one binary. It is the template with the most setup after install, because mail involves DNS, certificates and ports that the proxy does not handle.
| Image | stalwartlabs/stalwart:v0.16.21 |
| Web port | 8080, behind kamal-proxy (admin UI, JMAP, DAV) |
| Health check | /healthz/live |
| Published ports | 25, 465, 587, 143, 993, 110, 995, 4190 |
| Volumes | etc, data |
| Singleton | yes |
Before you start
- A server of its own if you can. Every port below is published on the machine itself, and two environments on one machine cannot both hold port 25.
- A hostname for the web admin and JMAP, e.g.
mail.example.com. Stalwart builds every URL in its JMAP and OAuth discovery documents from it, so the environment is blocked until it has one. - Control of the mail domain’s DNS. KamalCloud never writes DNS records.
- A plan for outbound port 25. Hetzner, DigitalOcean and AWS block it on new accounts, so sending mail needs a support ticket to the provider or a relay. Receiving is unaffected.
Installing
Templates → Stalwart → Install. Give it a name, a runner, an environment, the server and the domain. Install creates the app and seeds:
STALWART_PUBLIC_URL, set tohttps://plus the environment’s first domain at each deploy- The eight mail ports, in TCP, as the environment’s own. You can add or remove them afterwards under Hosts → Ports
- The
etcanddatavolumes, registered for backup
It does not deploy. Point an A record for the hostname at the server first.
Ports and the firewall
| Port | Protocol |
|---|---|
25 |
SMTP, server to server |
465 |
SMTP submission over TLS |
587 |
SMTP submission with STARTTLS |
143 / 993 |
IMAP / IMAP over TLS |
110 / 995 |
POP3 / POP3 over TLS |
4190 |
ManageSieve |
kamal-proxy only fronts HTTP, so these are published straight from the container. On an
API-managed server, every deploy opens them on the account’s cloud firewall. On a
self-hosted machine, opening them in whatever firewall it runs is
yours to do.
Because two containers cannot hold the same host port, an environment with published ports deploys by stopping the old container before booting the new one. Every deploy is a short mail outage. Sending servers retry, so inbound mail is delayed rather than lost.
First boot
- Deploy.
- Open Environment → Logs and look for bootstrap mode. Stalwart prints a temporary administrator password there on its first boot.
- Go to
https://<your domain>/adminand sign in with it. - Run the setup wizard with automatic TLS switched off.
kamal-proxyalready holds the certificate for the web hostname. - Copy the permanent administrator password from the wizard’s last screen. It is printed there and nowhere else.
- Deploy again so the new configuration takes effect.
Certificates for the mail ports
kamal-proxy holds ports 80 and 443 and gets the certificate for the web admin and JMAP. IMAPS
and SMTPS on 993 and 465, and STARTTLS on the others, need a certificate of their own, and the
proxy is not in that path.
Stalwart cannot answer an HTTP-01 or TLS-ALPN-01 challenge from behind the proxy, so in its admin UI either:
- set up ACME with a DNS-01 challenge, giving Stalwart an API token for your DNS provider, or
- upload a certificate and key you obtained elsewhere – remembering that nothing renews it.
Trusting the proxy
Web requests reach Stalwart from kamal-proxy, so without further configuration every client
looks like the proxy’s address. Under the HTTP listener’s settings, trust the proxy’s Docker
network so the client address is read from X-Forwarded-For.
DNS for the mail domain
Stalwart shows the records it wants for each domain in its admin UI. Publish them at your DNS provider. Typically:
| Record | Purpose |
|---|---|
A / AAAA for the mail hostname |
points at the server |
MX |
where other servers deliver mail for the domain |
TXT (SPF) |
which machines may send for the domain |
TXT (DKIM) |
the public key Stalwart signs outgoing mail with |
TXT (DMARC) |
what receivers do with mail that fails the two above |
SRV / autoconfig |
optional; lets mail clients find the server by themselves |
Set reverse DNS (the PTR record) for the server’s IP to the mail hostname as well. That is set at the cloud provider, not in your DNS zone, and much of the internet rejects mail from an address whose reverse DNS does not match.
Check DNS on the domain in KamalCloud only checks that the web hostname resolves to the server. It does not check mail records.
Locked out
If no administrator can sign in, add two variables:
| Variable | Value |
|---|---|
STALWART_RECOVERY_MODE |
1 |
STALWART_RECOVERY_ADMIN |
admin:<a password> – make it a secret |
Deploy, sign in with that account, fix what needs fixing, then remove both and deploy again. Leaving recovery mode on leaves a second way in.
Data and backups
- etc (
<app name>-etc) – Stalwart’s configuration - data (
<app name>-data) – mailboxes, calendars, contacts and indexes in RocksDB. It is marked to stop containers, because RocksDB writes continuously and a livetarof it does not restore. Each backup is a short outage.
Schedule both on their volumes. Back up etc alongside data: a mail store
restored without its configuration is not much use.
Upgrading
Change the image tag on the app and deploy. Stalwart’s release notes call out upgrades that migrate the data store; take a backup of both volumes first.
Troubleshooting
- Blocked: “Stalwart reads its own address” – the environment has no domain. Add one and deploy.
- No bootstrap password in the logs – the
etcvolume already holds a configuration from an earlier boot. Use the permanent password, or recovery mode. - Mail arrives but none goes out – outbound port 25 is still blocked by the provider.
- Outgoing mail lands in spam – check SPF, DKIM, DMARC and reverse DNS.
- Clients warn about the certificate on 993 – the mail listeners have no certificate of their own yet.
- Deploy fails on a port conflict – another container on the machine holds one of the mail ports.