Skip to content

Docker Mail Server: What Production Actually Needs

Short answer. A Docker mail server is a set of containers (an MTA such as Postfix, an IMAP server such as Dovecot, a spam filter such as rspamd, TLS termination, and usually a database) running on one Linux host with ports 25, 465, 587 and 993 exposed. Getting the containers running takes an afternoon. What decides whether it survives months of production is less visible: which state survives a container recreate, how DNS leaves the containers, whether health checks test real mail flow, and how updates are snapshotted and rolled back. This guide covers each of those, with what we learned running Vectis Mail in production.

Job Common choice In Vectis Mail
Send and receive (SMTP) Postfix Postfix
Mailboxes (IMAP/POP3) Dovecot Dovecot
Spam filtering rspamd rspamd, with a local DNS resolver (see below)
Antivirus ClamAV (optional) ClamAV, optional
TLS and HTTPS routing Traefik, nginx, Caddy Traefik, automatic Let’s Encrypt
Domains, mailboxes, aliases SQL database or flat files PostgreSQL
Cache and rate limits Redis-compatible store Valkey
Webmail Roundcube, SnappyMail Optional webmail
Management Admin UI, scripts Admin UI, REST API, CLI, update orchestrator

Any of these stacks can work. The differences that matter show up later, in the seven areas below.

Docker containers are disposable by design: docker compose up recreates a container whenever its image or settings change, and every update does exactly that. Anything not on a volume or bind mount is deleted at that moment.

The obvious state is usually handled: mailboxes, the database, TLS certificates. The ones that get missed:

  • DKIM private keys. Lose them and every domain sends unsigned mail until you regenerate keys and update DNS.
  • The mail queue. Postfix keeps accepted-but-undelivered mail in /var/spool/postfix. On a healthy server it’s nearly always empty, which is why a missing volume goes unnoticed, until the day a remote server is down and you update over deferred mail. We had exactly this gap in our own stack and wrote up how to check yours.

Check: list the mounts on each container (docker inspect --format '{{range .Mounts}}{{println .Destination}}{{end}}' <container>) and make sure every directory that holds data appears.

2. Configuration: bind mounts, not named volumes

Section titled “2. Configuration: bind mounts, not named volumes”

There are two ways to get configuration into a container, and one of them causes subtle bugs:

  • Named volumes are seeded from the image the first time they’re created and then never change. Ship a corrected config file in a new image and the container keeps using the old copy in the volume. Use named volumes for data.
  • Bind mounts of rendered config files from the host are what you want for configuration. One catch: many tools save a file by writing a new one and renaming it over the old, which gives it a new inode. A container with a single-file bind mount keeps reading the old file until it’s restarted. Either mount directories, or restart the service after a config change.

Vectis Mail renders every service’s configuration from one config file into a host directory, bind-mounts it, and reloads or restarts exactly the services whose files changed.

Containers resolve DNS through Docker’s embedded resolver, which forwards to whatever the host uses. For most things that’s fine. For a spam filter it often isn’t: the major DNS blocklists (Spamhaus, URIBL) refuse lookups that come through big shared resolvers, and the refusal looks like a normal DNS answer. On one of our servers, about a quarter of messages were being scored with the blocklists effectively off, and nothing logged an error. We wrote up the two-command check.

Vectis Mail runs a small local recursive resolver inside the spam-filter container so blocklist queries come from your server alone.

A container’s health check usually runs a command like postfix status or doveadm inside the container. Those report the process is running. They pass with an empty or broken configuration, a missing certificate, or a filter that never loaded its rules.

Better checks connect over the network the way a client would: open a TCP connection to port 25 and read the greeting, complete a TLS handshake on 465 and 993, and ask the spam filter to scan a known test message. The Vectis Mail dashboard probes Postfix, Dovecot, rspamd and Traefik over the network for this reason.

Mounting /var/run/docker.sock into a container gives it control of Docker, which on most hosts is equivalent to root. Some mail stacks mount it into several containers for convenience.

Keep it to the minimum: only the component that genuinely manages containers, read-only wherever it can be, and never anything internet-facing. In Vectis Mail the update orchestrator holds the socket for stack management, two narrow helpers have only what they need (signalling the mail containers when certificates renew, and, where log shipping is enabled, read-only log discovery), and Postfix, Dovecot and the web-facing services have none.

A single flat Docker network lets every container reach every other one, including the database, from the containers that face the internet. Split it up instead: a frontend network for HTTPS, a mail network for SMTP and IMAP, an internal data network for the database and cache that has no route out, and a separate management network. Then each container joins only the networks it needs. Vectis Mail uses four networks along those lines.

Image tags such as :latest or even :v1.2 can be repointed. For a mail server that holds your business email, an update should be:

  • Pinned to image digests (image@sha256:…), ideally from a signed release manifest, so what runs is exactly what was published.
  • Snapshotted first, with a database dump before any schema migration.
  • Health-gated, so a failed start stops the update instead of leaving the server half-upgraded.
  • Reversible, by restoring the snapshot and the previous images.

Vectis Mail’s updates run snapshot → migrate → pull → deploy → health check, with rollback, and vectis verify confirms the server runs exactly the images and binary published for its version.

  • Port 25, inbound and outbound. Many VPS providers block outbound 25 by default; get it lifted before you start. See how to get port 25 unblocked.
  • Reverse DNS (PTR) for your IP, matching your mail hostname.
  • SPF, DKIM and DMARC records for every domain. See our DKIM, SPF and DMARC guide.
  • Memory: 2 GB is workable without antivirus; 4 GB is comfortable. ClamAV alone needs more than 1 GB. Set per-container memory limits so one service can’t take the host down.

Building your own vs using a packaged stack

Section titled “Building your own vs using a packaged stack”

Building your own from individual images teaches you a lot and gives you full control, and every item above is solvable with care. A packaged stack is worth it when you’d rather inherit those decisions than make them all yourself. If you’re comparing options, our comparison of self-hosted email servers covers the main ones, and the installation guide shows what a Vectis Mail install involves, from a fresh Ubuntu VPS to a working server.