Skip to content
Serverküche
Search

Loading search … (only available on the published site).

Applications Difficulty: Intermediate

Stalwart: the lightweight mail server in a single container

Set up Stalwart Mail Server with Docker: SMTP, IMAP, JMAP and spam filter in a single, lean container – the modern alternative to Mailcow.

· 10 min read ·Duration: approx. 60 minutes
Table of contents

Your own mail server doesn’t have to consist of two dozen containers. Stalwart packs the complete mail server – SMTP, IMAP, JMAP, spam filter and storage – into a single container written in Rust. That makes it lean, fast and low-maintenance: the modern, lightweight alternative to heavyweights like Mailcow.

Mail servers stay demanding – no matter how lean

Stalwart is simpler to set up than classic stacks, but doesn’t take the core task of a mail server operator off your hands: deliverability. Whether your mails arrive or land in spam is decided by SPF, DKIM, DMARC and a clean reverse-DNS entry – not the software. That’s exactly what we cover in the follow-up tutorial. Whoever doesn’t fancy this ongoing maintenance is often better off with a reputable mail host. That’s an honest trade-off, not a weakness.

What are we building?

By the end, Stalwart 0.16.14 runs as a single Docker container on your server and provides everything a full-featured mail server needs:

  • SMTP (send/receive) on ports 25, 465 and 587,
  • IMAP (993) and the modern JMAP for fast, efficient clients,
  • ManageSieve (4190) for server-side filter rules,
  • a built-in spam and phishing filter (no separate Rspamd/ClamAV needed),
  • integrated storage (RocksDB) – no external database,
  • a modern web interface to manage domains, accounts and settings.

The big difference from Mailcow: it’s one process, one container, a few hundred megabytes of image. That significantly reduces RAM demand, attack surface and maintenance effort. Tested with Docker 29 on Debian 13.

Why is the lean approach worth it? A classic mail server stack consists of a good dozen services – Postfix, Dovecot, Rspamd, a database server, a virus scanner, Redis, a web server – all of which run individually and have to be updated and tuned to each other. Each of them is a potential source of error and a piece of attack surface. Stalwart bundles the same functionality into a single application written in Rust. Rust also brings memory safety out of the box – for software that accepts unfiltered data from the entire internet, that’s a real security argument.

Prerequisites

  • A dedicated server with Debian 13 and Docker installed, on which the mail and web ports are free (no reverse proxy occupying 25/443).
  • A domain whose DNS you control yourself – for the mail server’s A record and the MX record.
  • Outbound port 25 must be open. Many providers block it against spam; at netcup you enable it via a support ticket. Without an open port 25 you can’t deliver mail to other servers.
  • A PTR/reverse-DNS entry for the server IP (in the netcup SCP) that points to your mail hostname.

Stalwart is frugal – a small server is enough to start. How much your setup needs in total is estimated by the server calculator.

🍳 Recommendation Ad

VPS 1000 G12

4 vCores · 8 GB RAM · 256 GB NVMe

from €10.36/month

Stalwart is lean – the VPS 1000 is quite enough to start.

Go to netcup →

💶 5 € voucher for new netcup customers:36nc17844976032 (new customers only, no domains)

Step by step

Step 1: Create the DNS records

First set up the DNS entries – they need time to propagate. YOUR_DOMAIN is your mail domain, YOUR_SERVER_IP the server’s IPv4 address:

Ausgabe
mail.YOUR_DOMAIN.   A     YOUR_SERVER_IP
YOUR_DOMAIN.        MX    10 mail.YOUR_DOMAIN.

The MX record refers other mail servers to mail.YOUR_DOMAIN. This hostname is your server’s identity. SPF, DKIM and DMARC we add in the deliverability tutorial – Stalwart generates the DKIM keys itself during setup.

Step 2: Write the compose.yaml

Create a folder and change into it:

Terminal
mkdir -p /opt/stalwart && cd /opt/stalwart

Create the file compose.yaml:

YAML
services:
  stalwart:
    image: stalwartlabs/stalwart:v0.16.14
    container_name: stalwart
    restart: unless-stopped
    ports:
      - "25:25"       # SMTP (server-to-server)
      - "465:465"     # SMTPS (send, implicit TLS)
      - "587:587"     # Submission (send, STARTTLS)
      - "143:143"     # IMAP
      - "993:993"     # IMAPS
      - "4190:4190"   # ManageSieve
      - "8080:8080"   # web interface & JMAP (initial setup)
      - "443:443"     # HTTPS (after setup, own certificate)
    volumes:
      - stalwart_data:/opt/stalwart
volumes:
  stalwart_data:

The most important thing: one service, one volume for all data. Stalwart fetches its TLS certificate later itself via Let’s Encrypt – you don’t need a separate reverse proxy for that. Make sure port 443 is free on the host (Stalwart also uses it for the certificate retrieval, see step 4).

Step 3: Start the container and get the startup password

Start Stalwart:

Terminal
docker compose up -d

On the very first start, Stalwart runs in bootstrap mode: there’s no configuration yet, and port 8080 is open for the initial setup. In the process it generates a one-time administrator password that you read from the logs:

Terminal
docker compose logs | grep -A2 "temporary administrator"
Ausgabe
🔑 Stalwart bootstrap mode - temporary administrator account
   password: <ONE-TIME-PASSWORD>

Note this password – it’s shown only once. (If you’d rather set a fixed password, you can store a STALWART_RECOVERY_ADMIN=admin:YOUR_PASSWORD under environment: in the compose.yaml.)

Step 4: Log in and run through the setup wizard

Open the web interface in the browser: http://YOUR_SERVER_IP:8080/admin. You land on the login. Log in as user admin with the one-time password from step 3 – the input is done in two steps (first username, then password):

The login form of the Stalwart web interface with a field for the username.
The login of the Stalwart web interface.

After logging in, the setup wizard greets you. In the first step you set the server identity:

  • Server Hostname: mail.YOUR_DOMAIN – the FQDN from your A record.
  • Default Email Domain: YOUR_DOMAIN – your mail domain.
  • Automatically Obtain TLS Certificate – leave enabled; Stalwart fetches the Let’s Encrypt certificate itself. For that, port 443 must be reachable from outside: Stalwart uses the TLS-ALPN-01 challenge over 443 by default, it doesn’t need port 80 for it.
  • Generate Email Signing Keys – leave enabled; that generates your DKIM keys right away.

The Stalwart initial setup wizard with the fields for server hostname and default domain.
Step 1 of the wizard: the server identity.

Click through the further steps of the wizard:

  • Storage: the default RocksDB is an embedded key-value store and perfect for single-server setups – no external database server needed. Only for large, clustered installations do you reach for PostgreSQL or similar.
  • Directory: the internal directory manages accounts and passwords directly in Stalwart. Whoever already runs a central LDAP/Active Directory can connect it here instead – for getting started you stay with the internal directory.
  • Logging: defines how verbosely Stalwart logs. Info is a good compromise in everyday use; on problems you briefly turn up to Debug.
  • DNS: for the start, manual management is enough – Stalwart shows you the records to create later under the respective domain.

At the end, Stalwart generates your permanent administrator account and shows its password a single time – write it down immediately. From now on the bootstrap mode is over and port 8080 is no longer open for setup; you reach the console in future via HTTPS.

Already using a reverse proxy?

Unlike classic mail servers, Stalwart can run its web interface behind an existing Traefik without problems – then simply route the HTTP port 8080 there. The mail ports (25, 465, 587, 993 …) must still be directly at the server, though, because they don’t speak HTTP and can’t be routed through an HTTP proxy.

Step 5: Create a domain and first mailbox

After the setup you’re in the management console. Via the left navigation you reach Directory → Domains and Directory → Accounts. If needed, create further domains and then your first mailbox:

The Stalwart management console with the accounts overview.
The management console: manage accounts.

Via Create account you create a mailbox – email address, display name and a strong password (password manager!). This account can then log in via IMAP/JMAP and SMTP:

The form for creating a new mailbox in Stalwart.
Create a new mailbox.

Step 6: Set it up in a mail program

Enter the new account in your mail program. Stalwart supports autodiscovery, so usually the email address and password are enough. Manually you use:

Ausgabe
IMAP:        mail.YOUR_DOMAIN, port 993, SSL/TLS
SMTP:        mail.YOUR_DOMAIN, port 587, STARTTLS
User:        the full email address

Besides the tried-and-true IMAP, Stalwart also speaks JMAP – a modern, JSON-based protocol designed for today’s networks. JMAP transfers only real changes instead of re-syncing whole folders, gets by with a single connection and is thus noticeably more sparing with battery and data – especially on the phone. If your mail client supports JMAP (e.g. the Thunderbird family in newer versions), you benefit directly; otherwise you stay with IMAP, which works just the same.

A mail from you to yourself should land in the inbox immediately – that proves that local delivery, IMAP and SMTP are in place. The acid test, though, is sending to the outside and receiving from the outside, and there deliverability decides.

Note

Check your deliverability after the setup with a service like mail-tester.com: it rates SPF, DKIM, DMARC and reverse DNS. How you reach the full score is the topic of the deliverability tutorial.

Step 7: Spam filter and Sieve rules

A big advantage of Stalwart: the spam and phishing filter is already built in and active – you don’t have to run and maintain a separate Rspamd container. It rates incoming mails based on numerous features (reputation, SPF/DKIM/DMARC result, content heuristics) and marks or blocks suspicious ones. In the console under Settings → Spam Filter you can adjust the sensitivity and put senders on allow or block lists.

For your own rules on incoming and outgoing mail, Stalwart supports Sieve – the standardized filter language. With it you sort e.g. newsletters automatically into a folder or forward certain senders. You manage Sieve scripts server-side (via ManageSieve on port 4190) or directly in the web interface, so the rules take effect independent of the mail program – even when your computer is off.

Tip

Because the filter works server-side, it applies to all your devices at once. A Sieve rule created once or a spam decision takes effect on the phone just like on the laptop – unlike client-side filters you’d have to set up anew on every device.

When things go wrong

The web interface on port 8080 doesn’t respond. the container is still booting or port 8080 is occupied/blocked. check docker compose ps, read docker compose logs and make sure the firewall lets port 8080 (and later 443) through.

I missed the bootstrap password. it’s only logged once. set a STALWART_RECOVERY_ADMIN=admin:YOUR_PASSWORD under environment: in the compose.yaml and restart with docker compose up -d – that gives you a fixed recovery-admin account.

No TLS certificate, the HTTPS address shows a warning. Let’s Encrypt can’t reach your server on port 443 (Stalwart uses the TLS-ALPN-01 challenge over 443), or the A record of mail.YOUR_DOMAIN is wrong. check the A record and open port 443 in the firewall – Stalwart retries the certificate request automatically afterwards.

Mails to the outside stay stuck, logs show timeouts on port 25. your provider blocks outbound SMTP traffic on port 25. have port 25 enabled outbound at the provider (at netcup via a ticket). Without it, no delivery to other servers is possible.

Other servers don’t accept your mails or they land in spam. missing or wrong PTR entry, no SPF/DKIM/DMARC. that’s not a Stalwart error but a matter of DNS/reputation configuration – see the deliverability tutorial.

Maintenance & backups

Updates. Stalwart is a single container – an update is correspondingly simple:

Terminal
cd /opt/stalwart
docker compose pull && docker compose up -d

Before bigger jumps, read the release notes on GitHub; stay on a specific version tag (like v0.16.14 here) instead of latest so updates remain reproducible.

Backups. All data – messages, accounts, configuration and the DKIM keys – lives in the stalwart_data volume. Back up this volume regularly away from the server, cleanest encrypted with Restic. Because Stalwart writes the data during operation, you back up most consistently by briefly stopping the container (docker compose stop), backing up the volume and starting again. The DKIM keys are especially important: if they’re lost, your signatures break after a restore until you reset the DNS records. A backup you’ve never restored is just a hopeful guess – test the restoration once on a test system.

Keep an eye on reputation. Occasionally check whether your server IP has landed on a block list, and take a look at the Stalwart console under Observability. A compromised mailbox that sends spam quickly ruins the reputation of your entire domain.

You might also like