How to Deploy docker-mailserver on a VPS
Updated Jul 2026
verified on Ubuntu 26.04 · Jul 2026Self-host docker-mailserver — a production-ready, config-file-driven Postfix + Dovecot + Rspamd mail server in one container — on your own VPS, and understand what real mail delivery still requires.
- A VPS with 1 vCPU / 512 MB RAM (roughly 3 GB if you turn on ClamAV — see sizing below)
- A fresh Ubuntu 26.04 server with root/sudo SSH access, Docker Engine + Compose installed
- A real domain you control, with the ability to set MX, TXT (SPF/DKIM/DMARC), and PTR records — required for actual mail delivery, not for the install itself
- Your VPS provider's confirmation that outbound port 25 is unblocked, or a request filed to unblock it — most providers block it by default to fight spam
What docker-mailserver is
docker-mailserver (DMS) is a production-ready mail
server that packs Postfix, Dovecot, and Rspamd into a single container,
configured entirely from plain files — no database, no web UI, no admin panel
to lock down. You edit a couple of config files and run a setup CLI inside
the container to manage mailboxes, aliases, and DKIM keys. It's MIT-licensed
and it's the closest thing self-hosting has to a "just run it" mail stack,
which is notable because self-hosted mail has a reputation for being the
hardest thing on this site to get right.
The appeal is the usual one: cost and ownership. Google Workspace bills per mailbox, forever. A VPS running docker-mailserver costs the same flat fee whether it serves one mailbox or a dozen, and your mail lives on infrastructure you control rather than in a vendor's account you're renting.
Set expectations correctly before you start. Running the container is the easy part — this guide gets you a booted, working mail server in about the time it takes to read it. Getting mail delivered to Gmail, Outlook, and everyone else's inbox is a separate, ongoing project that depends on DNS records, IP reputation, and a domain with history, none of which a fresh VPS has on day one. This guide is honest about that split: it verifies install and boot, and it hands you a clearly labeled checklist for the deliverability work that comes after.
Server sizing
docker-mailserver is light by default. Postfix and Dovecot are efficient C/mature software, and with the anti-virus scanner off, the whole stack — Postfix, Dovecot, Rspamd, OpenDKIM, OpenDMARC — comfortably runs in 1 vCPU and 512 MB of RAM. That's enough for a personal domain or a small team's mailboxes.
The one setting that changes the math is ClamAV, the anti-virus scanner.
It's off (ENABLE_CLAMAV=0) in the default mailserver.env, and for good
reason: ClamAV's virus-definition database alone eats well over a gigabyte of
RAM once loaded, pushing a realistic floor to roughly 3 GB if you turn it
on. Rspamd already handles spam filtering without it, so many self-hosters
leave ClamAV off and accept that attachment scanning isn't happening — a
reasonable trade-off for a personal or small-team server, less so if you're
handling mail for people who might click on something bad. Decide deliberately
rather than defaulting into either choice.
For disk, 20 GB is comfortable for mailboxes measured in the tens of gigabytes; size up if you're expecting heavy attachment volume or a long retention policy. A Hetzner CX22 covers the 512 MB case with room to spare; step up a tier if you're turning ClamAV on.
Prepare the server
This guide assumes you've already worked through Docker & Compose on Ubuntu — a fresh Ubuntu 26.04 box with Docker Engine and the Compose plugin installed. docker-mailserver has no web UI and sits behind no reverse proxy, so unlike most guides on this site there's no port to keep private: SMTP and IMAP have to be reachable from the internet directly, on their own standard ports.
sudo ufw allow 25/tcp
sudo ufw allow 143/tcp
sudo ufw allow 465/tcp
sudo ufw allow 587/tcp
sudo ufw allow 993/tcp
sudo ufw status verbose
The Docker-and-ufw gotcha, this time in your favor. Other guides on this
site warn that Docker writes its own iptables rules and publishes container
ports regardless of what ufw says — meaning a port you thought was private
via ufw deny can still be reachable. Here that same behavior is exactly what
you want: docker-mailserver's compose file publishes to 0.0.0.0 by design,
because mail has to be publicly reachable. The ufw rules above are for a
consistent, self-documenting firewall policy, not because they're the thing
actually gating access — Docker's own rules are.
Install docker-mailserver
DMS ships an official compose.yaml and mailserver.env — fetch both rather
than hand-writing them, since the project keeps them current with the image:
mkdir -p ~/mailserver && cd ~/mailserver
DMS_URL="https://raw.githubusercontent.com/docker-mailserver/docker-mailserver/master"
wget "${DMS_URL}/compose.yaml"
wget "${DMS_URL}/mailserver.env"
The compose file's only required edit is the mail server's hostname —
it ships as the placeholder mail.example.com. For a real deployment, edit
that value in compose.yaml to your own FQDN by hand (say mail.example.com,
with your DNS MX record pointing at it). For this install we don't have a
real domain yet, so we derive a working one from the box's own public IP using
sslip.io — a DNS service that resolves any
<anything>.<ip>.sslip.io name straight to <ip> — and set it non-interactively:
export SERVER_IP="$(curl -s https://ifconfig.me)"
export MAIL_DOMAIN="mail.${SERVER_IP}.sslip.io"
sed -i "s/mail.example.com/${MAIL_DOMAIN}/" compose.yaml
grep "hostname:" compose.yaml
Bring the stack up:
docker compose up -d
docker compose logs --tail 40 mailserver
You have a two-minute window. On first boot, DMS waits for at least one mailbox to be added — if none shows up within two minutes, it assumes something's wrong, shuts down, and restarts to try again. That's not a bug to work around; it's why the very next step happens immediately, before you go read documentation or grab coffee.
Create your first mailbox, then enable DKIM
Add a mailbox with setup email add. Normally it prompts interactively for a
password (so the password never lands in your shell history) — but it also
accepts the password as a second argument, which is what makes this scriptable:
sleep 10
export MAIL_USER="postmaster@${MAIL_DOMAIN}"
export MAIL_PASSWORD="$(openssl rand -base64 24)"
docker exec -i mailserver setup email add "${MAIL_USER}" "${MAIL_PASSWORD}"
postmaster@ is a deliberate choice, not just a placeholder — RFC 5321
requires every mail domain to have a working postmaster mailbox, and
mailserver.env's POSTMASTER_ADDRESS defaults to exactly this address for
bounce and report delivery. For a real domain you'll add your actual
mailboxes the same way (setup email add you@example.com, letting it prompt
for a password interactively so it never touches shell history), and
postmaster@ is one you'll want to exist regardless.
Confirm it landed, then generate DKIM keys — setup config dkim runs
non-interactively with sane defaults (2048-bit key, mail selector, the
domain(s) already known to DMS from the mailbox you just added):
docker exec -i mailserver setup email list
docker exec -i mailserver setup config dkim
The generated public key — the value you'll publish as a DNS TXT record — is written to the config volume on the host:
cat "docker-data/dms/config/opendkim/keys/${MAIL_DOMAIN}/mail.txt"
Give the container a moment, then confirm it's reported healthy — the image
ships its own internal healthcheck, which docker compose ps surfaces:
sleep 20
docker compose ps
That's the entire install: a running container, a mailbox, and a DKIM keypair. Everything from here is either backup hygiene or the DNS work that turns "a mail server exists" into "mail actually arrives."
DNS & deliverability — read this before you send real mail
This is the part the verified install-and-boot check above does not, and
cannot, cover. MX, SPF, DKIM, DMARC, PTR, and an unblocked port 25 all
require a real domain and a server with a bit of sending history — none of
which a fresh VPS on a throwaway sslip.io hostname has. Treat this section
as your go-live checklist, not something this guide can verify for you:
- MX record. Point your domain's MX record at your mail server's FQDN
(
mail.example.com), not at the bare domain and not at an IP. - SPF (TXT record on the bare domain). Publish
v=spf1 mx ~all(or-aif you want a hard fail) so receiving servers know your mail server is authorized to send for your domain. - DKIM (TXT record). Publish the key generated above as a TXT record at
mail._domainkey.<your-domain>— the file youcat-ed contains the exact record, pre-formatted for a DNS zone file. - DMARC (TXT record at
_dmarc.<your-domain>). Start permissive while you confirm SPF/DKIM are passing —v=DMARC1; p=none; rua=mailto:postmaster@<your-domain>— then tighten top=quarantineorp=rejectonce reports look clean. - PTR (reverse DNS). Your VPS provider — Hetzner, Kamatera, DigitalOcean, all of them — sets this from their control panel or support desk, not from DNS you manage. It must resolve your IP back to your mail hostname exactly, or major providers will bounce or spam-bin your mail outright.
- Port 25 outbound. Most VPS providers block it by default, precisely because unblocked port 25 on cheap, disposable VPS instances is a top spam vector. Check your provider's policy and file an unblock request (often tied to identity verification) before you assume delivery is broken — it might just be a blocked port.
None of this is optional for real mail, and none of it is fast — DNS propagation and IP-reputation warm-up both take time. Budget days, not minutes, between "server is up" and "mail reliably lands in Gmail's inbox."
Backups
Three things live in ~/mailserver/docker-data/dms/, and all three matter:
mail-data/— the actual mailboxes: every message, every folder.mail-state/— Postfix/Dovecot working state (indices, queues).config/— mailbox definitions, aliases, and critically, the DKIM private keys. Lose this without a backup and you'll regenerate DKIM keys from scratch, which means re-publishing DNS and losing signing continuity — annoying, not catastrophic, but avoidable.
cd ~/mailserver
tar czf "mailserver-backup-$(date +%F).tar.gz" \
docker-data/dms/mail-data docker-data/dms/mail-state docker-data/dms/config
Ship that archive off the box on a schedule — a server that dies with its only backup sitting next to it hasn't been backed up. Test a restore into a throwaway instance now and then; an archive you've never restored is a guess.
Upgrades
cd ~/mailserver
docker compose pull
docker compose up -d
Pull the newer image and recreate the container against your unchanged volumes — mailboxes, keys, and config all survive. Read the release notes before a major version bump; mail software is conservative but not immune to occasional config-format changes.
Troubleshooting
Mail sends fine internally but never reaches Gmail/Outlook. This is
almost always the deliverability checklist above, not a DMS bug. Check SPF,
DKIM, and DMARC are all passing with a tool like mail-tester.com, confirm
your PTR record matches your hostname, and confirm port 25 isn't blocked
outbound by your provider.
Container restarts in a loop shortly after docker compose up -d. You
likely missed the two-minute mailbox window. Check
docker compose logs --tail 100 mailserver for the shutdown reason, then
re-run docker compose up -d followed immediately by setup email add.
setup email add or setup config dkim hangs or errors with "no such
container." The container isn't up yet, or the name doesn't match. Confirm
with docker compose ps — the service is named mailserver in the official
compose file — and give it a few more seconds after up -d before running
setup commands.
DKIM TXT record doesn't match what receivers see. DNS providers sometimes split long TXT records into quoted segments automatically; if you're pasting the generated record by hand, use your provider's TXT record field as-is rather than reformatting it — most parse the OpenDKIM output's line breaks correctly, but paste the whole block if unsure.
docker compose logs -f mailserver
Verification + next steps
What this guide verifies: the container installs, boots, reports
healthy, accepts a mailbox via setup email add, and generates a DKIM
keypair via setup config dkim — all on a fresh Ubuntu 26.04 VPS. That's the
verified stamp's actual scope.
What it deliberately does not verify: that mail sent from this server
lands in anyone's inbox. Real deliverability depends on DNS records, PTR
setup, and port 25 access this guide can only document, not test from a
throwaway box. Work through the DNS & deliverability checklist above with
your real domain, then send a test message to a checker like
mail-tester.com before you trust this server with real mail.
From there, it's routine maintenance: back up config/ and mail-data/ on a
schedule, keep the image current, and watch your sending reputation the way
you'd watch any production service. A Hetzner CX22 comfortably
covers the default (ClamAV-off) footprint; size up to Kamatera's larger tiers
if you turn ClamAV on. See
Best VPS for self-hosting for the ranked picks.