mailcow: dockerized
A complete mail server in 18 containers, with a UI that makes it survivable
mailcow: dockerized bundles Postfix, Dovecot, Rspamd, SOGo and a web admin UI into one Docker Compose stack. It replaces a Google Workspace or Microsoft 365 mailbox with mail you host on a domain and an IP you control.
Our verdict on mailcow: dockerized#
Self-hosted mail is the highest-risk thing in this whole category and mailcow does not change that. It makes Postfix, Dovecot and Rspamd tractable; it does nothing about the facts that most residential ISPs and several major clouds block outbound port 25 by default, that you cannot set a PTR record for an address you do not control, and that a fresh IP has no sending reputation, so Gmail and Outlook will greylist or bin you for weeks. Answer three questions before you install: can you open outbound 25, can you set reverse DNS to your mail hostname, and is your IP range already listed on the usual blocklists. If any answer is no, either stop or run mailcow with outbound relayed through an SMTP provider and keep only receipt and storage local. Given a clean IP with correct rDNS, this is the best-documented mail stack there is: 18 containers, a documented 6 GiB floor, one update.sh. Receiving mail is easy. Sending it reliably is the entire job.
Who mailcow: dockerized is for#
Choose it if
- A small business or association on a static IP with a controllable PTR record, leaving Microsoft 365 for cost or jurisdiction reasons.
- Anyone who wants webmail, calendar and contacts through SOGo from the same box as IMAP, rather than mail alone.
- Operators happy to relay outbound through Postmark, SES or Mailjet and self-host only receipt, storage and filtering.
- People who want a UI that manages DKIM keys, aliases, quotas and spam policy instead of hand-editing Postfix maps.
Look elsewhere if
- You are on a residential connection, a dynamic IP, or a provider that will not unblock port 25. You will not deliver, and no configuration fixes it.
- You have under 6 GiB of RAM. That is a documented minimum for the default configuration, not a suggestion.
- Losing three days of mail would be a serious problem and you have never rehearsed a restore of this stack.
What running it actually looks like#
One git clone, generate_config.sh, then docker compose up -d brings up 18 containers: Postfix, Dovecot, Rspamd, ClamAV, SOGo, MariaDB, Redis, nginx, php-fpm, unbound, acme, netfilter, watchdog, dockerapi, memcached, olefy, ofelia and postfix-tlspol. Everything hangs off mailcow.conf, and MAILCOW_HOSTNAME is effectively immutable: changing it after mail exists means reissuing certificates and regenerating DKIM. Before you start, DNS must already have an A record for that hostname, an MX pointing at it, and a PTR record set at your IP provider matching it exactly. Ports 25, 465, 587, 143, 993, 110, 995, 4190, 80 and 443 all need to reach the host, and the acme container wants 80 free. Updates are ./update.sh, which pulls new image tags and restarts the stack.
Resource profile#
- Memory
- 3500 MB idle, around 6144 MB in ordinary use. The docs state a minimum of 6 GiB plus 1 GiB of swap for the default configuration. ClamAV and the Flatcurve full-text index are the greedy components and can both be disabled if you are tight.
- CPU and acceleration
- 1 GHz is the documented floor. Rspamd and ClamAV spike on every inbound message, and the first full-text index build over a freshly migrated mailbox will saturate a core for a while.
- Storage growth
- 20 GiB before any mail, per the docs. The `vmail` volume grows with mailboxes, the Flatcurve index adds a further fraction on top, and Rspamd's Redis-backed Bayes data creeps up slowly.
- Operational load
- 4 of 5, Heavy. Eighteen coupled containers, a MariaDB, a crypt volume that decides whether your backup is readable, and update batches applied through `update.sh`. On top of that sits the permanent external job of keeping DNS, rDNS and IP reputation healthy, which no amount of container tidiness removes.
Figures describe a small single-household install and are the working assumptions behind our stack planner. Your numbers will differ with library size, user count and hardware. See methodology.
Data and backup surface#
helper-scripts/backup_and_restore.sh backup all covers the six components that matter: vmail, crypt, redis, rspamd, postfix and mysql. The crypt volume is the one people forget; it holds the Dovecot mail-crypt keys, and without it your backed-up vmail is unreadable ciphertext, so never capture vmail without crypt from the same moment. Keep mailcow.conf and any data/assets overrides alongside. A live rsync of vmail is acceptable because maildir files are write-once, but MariaDB and Redis need the script's dumps rather than a file copy. ClamAV signatures and the Flatcurve index are regenerable and can be excluded.
Derived backup shape
MySQL dump. mariadb-dump / mysqldump with --single-transaction, then snapshot the dump plus the app's data volume.
Traps and surprises#
The crypt volume decides whether your backup is readable
Dovecot stores mail encrypted and the keys live in the crypt volume, separately from vmail. A backup of vmail alone restores perfectly and gives you a maildir tree nobody can open. The official script backs up crypt as one of its components for exactly this reason. Take vmail and crypt in the same run, verify by restoring to a scratch host and actually opening a message.
MAILCOW_HOSTNAME cannot be changed later
The hostname in mailcow.conf is written into certificates, DKIM records, SOGo configuration and the SMTP banner. Changing it on a running system is not a supported edit; the practical path is a fresh install and a migration of mailboxes. Pick the hostname you will still want in three years, usually mail.yourdomain.tld, before the first generate_config.sh, and set the PTR record to match it exactly.
A Debian 12 to 13 upgrade can steal port 25
The mailcow docs call this out specifically: upgrading the host from Debian 12 to Debian 13 can pull in Exim, which binds port 25 and quietly stops Postfix from accepting inbound mail. The symptom is mail bouncing at other people's servers while your stack looks healthy. Check with ss -lntp | grep :25 after any host distribution upgrade and remove or mask the offending MTA.
PTR is set at your IP provider, not in your DNS zone
Reverse DNS lives with whoever owns the IP block, so it is a control panel setting at Hetzner, OVH, DigitalOcean or your ISP, not a record you add to your domain. The docs require the PTR to match your mailcow hostname's FQDN, for IPv4 and IPv6 both. A missing or mismatched PTR is the single most common reason a technically perfect mailcow gets rejected by Outlook and Gmail.
ClamAV and Flatcurve are why the floor is 6 GiB
The documented minimum of 6 GiB plus 1 GiB swap is mostly those two: ClamAV loads its full signature database into memory, and the Flatcurve full-text search engine indexes every message. Both can be disabled in mailcow.conf if you are running on a 4 GiB box, at the cost of virus scanning and fast IMAP search respectively. Disabling them is a supported choice; running the default stack under-provisioned is not.
Specifications#
- Category
- Chat, mail and notifications
- Licence
- GPL-3.0 for mailcow's own code; bundled components keep their own licenses (Copyleft)
- Written in
- PHP / shell, orchestrating Postfix, Dovecot and Rspamd
- First release
- 2016
- Maturity
- Mature
- Datastore
- MariaDB 10.11 plus Redis
- Default ports
25,80,110,143,443,465,587,993,995,4190- Ships as
- docker compose
- arm64 builds
- Yes
- Identity
- Native OIDC. Identity providers are configured in the mailcow UI itself: Keycloak, generic OIDC and LDAP, with automatic user creation on first login; mail clients still need app passwords unless you use the Mailpassword flow.
- Replaces
- Google Workspace Gmail, Microsoft 365 Exchange Online, Fastmail, Zoho Mail
- Project site
- mailcow.email
- Source
- github.com/mailcow/mailcow-dockerized
- Documentation
- docs.mailcow.email
Alternatives to mailcow: dockerized#
Everything else in chat, mail and notifications, closest in operational weight first.
| Tool | Ops load | Idle RAM | Licence |
|---|---|---|---|
| Synapse | 4, Heavy | 700 MB | AGPL-3.0-only (or a commercial license from Element) |
| Mattermost | 3, Moderate | 500 MB | AGPL-3.0 core, Mattermost Source Available License for `server/enterprise/`, Apache-2.0 for webapp and admin tools, MIT for official binaries |
| Stalwart | 3, Moderate | 150 MB | AGPL-3.0 for the Community edition, dual licensed with the proprietary Stalwart Enterprise License for gated features |
| Gotify | 1, Set and forget | 20 MB | MIT |
| ntfy | 1, Set and forget | 30 MB | Apache-2.0 (dual licensed with GPL-2.0) |
Where mailcow: dockerized comes up elsewhere#
Migration guides
Sources#
Facts on this page were checked against the project's own material. Where a number is our own estimate rather than a published figure, the text says so.
Published . Last reviewed . Found something out of date? Tell us and we will fix it and log the change.