Skip to content

How to Deploy docker-mailserver on a VPS

Updated Sep 2026

verified on Ubuntu 26.04 · Jul 2026
We earn commissions when you shop through the links below. Full disclosure →

Self-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.

Before you start
  • 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
Need a box for this guide? Kamatera's free tier lets you spin one up now.Start free on Kamatera → (opens in new tab)

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 2 vCPU / 4 GB VPS 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.

Where to host itaffiliate disclosure
Hetzner Cloudrun it on
2 vCPU · 4 GB RAM · 80 GB SSD · $23.59/mo
Get Hetzner Cloud (opens in new tab)
Kamaterafree trial
1 vCPU · 1 GB RAM · 20 GB SSD · $4.00/mo
Start free on Kamatera → (opens in new tab)
DigitalOceanalso works on
1 vCPU · 1 GB RAM · 25 GB SSD · $6.00/mo
Deploy on DigitalOcean → (opens in new tab)

Paid link — we earn a commission if you shop through it.

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 -a if 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 you cat-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 to p=quarantine or p=reject once 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 2 vCPU / 4 GB box comfortably covers the default (ClamAV-off) footprint; size up a tier if you turn ClamAV on. See Best VPS for self-hosting for the ranked picks.

Next steps

How to self-host docker-mailserver →More self-hosted mail server tools →Best VPS for a Mail Server →Automatic HTTPS with Caddy →Run Claude Code with Ollama on Your Own VPS →Deploy Coolify on a VPS →How to Deploy Actual Budget on a VPS →How to Deploy AnythingLLM on a VPS →How to Deploy Appwrite on a VPS →How to Deploy Audiobookshelf on a VPS →How to Deploy Authelia on a VPS →How to Deploy authentik on a VPS →How to Deploy Baserow on a VPS →How to Deploy Beszel on a VPS →How to Deploy Bitwarden on a VPS →How to Deploy BookStack on a VPS →How to Deploy CapRover on a VPS →How to Deploy Checkmate on a VPS →How to Deploy Directus on a VPS →How to Deploy Docmost on a VPS →How to Deploy Dokku on a VPS →How to Deploy Dokploy on a VPS →How to Deploy Firefly III on a VPS →How to Deploy Forgejo on a VPS →How to Deploy Gatus on a VPS →How to Deploy Ghostfolio on a VPS →How to Deploy Gitea on a VPS →How to Deploy GitLab on a VPS →How to Deploy GlitchTip on a VPS →How to Deploy Grafana on a VPS →How to Deploy Graylog on a VPS →How to Deploy Headscale on a VPS →How to Deploy Healthchecks on a VPS →How to Deploy Home Assistant on a VPS →How to Deploy Immich on a VPS →How to Deploy Jan on a VPS →How to Deploy Jellyfin on a VPS →How to Deploy Karakeep on a VPS →How to Deploy Keycloak on a VPS →How to Deploy Leantime on a VPS →How to Deploy LibreChat on a VPS →How to Deploy Linkwarden on a VPS →How to Deploy LocalAI on a VPS →How to Deploy Mailcow on a VPS →How to Deploy Mailu on a VPS →How to Deploy Matomo on a VPS →How to Deploy Mattermost on a VPS →How to Deploy Meilisearch on a VPS →How to Deploy Memos on a VPS →How to Deploy n8n on a VPS →How to Deploy Navidrome on a VPS →How to Deploy NetBird on a VPS →How to Deploy Netdata on a VPS →How to Deploy Nextcloud on a VPS →How to Deploy Next.js to a VPS →How to Deploy Nginx Proxy Manager on a VPS →How to Deploy NocoDB on a VPS →How to Deploy ntfy on a VPS →How to Deploy Ollama on a VPS →How to Deploy Open WebUI on a VPS →How to Deploy OpenHands on a VPS →How to Deploy OpenObserve on a VPS →How to Deploy OpenProject on a VPS →How to Deploy Outline on a VPS →How to Deploy Pangolin on a VPS →How to Deploy Paperless-ngx on a VPS →How to Deploy Passbolt on a VPS →How to Deploy Plane on a VPS →How to Deploy Plausible Analytics on a VPS →How to Deploy Pocket ID on a VPS →How to Deploy PocketBase on a VPS →How to Deploy Prometheus on a VPS →How to Deploy Psono on a VPS →How to Deploy Radarr on a VPS →How to Deploy Rocket.Chat on a VPS →How to Deploy SigNoz on a VPS →How to Deploy Sonarr on a VPS →How to Deploy Stalwart on a VPS →How to Deploy Stirling-PDF on a VPS →How to Deploy Supabase on a VPS →How to Deploy Synapse on a VPS →How to Deploy Taiga on a VPS →How to Deploy TeamPass on a VPS →How to Deploy Tinyauth on a VPS →How to Deploy Traefik on a VPS →How to Deploy Trilium on a VPS →How to Deploy Twenty CRM on a VPS →How to Deploy Umami on a VPS →How to Deploy Uptime Kuma on a VPS →How to Deploy Vaultwarden on a VPS →How to Deploy Vikunja on a VPS →How to Deploy wg-easy on a VPS →How to Deploy Wiki.js on a VPS →How to Deploy Zabbix on a VPS →How to Deploy Zitadel on a VPS →How to Deploy Zulip on a VPS →Docker & Compose on Ubuntu 26.04 →Building AI Workflows with n8n →Install Open WebUI with Ollama →Adding AI-Powered Insights to Plausible Analytics →Building AI-Powered Apps with Supabase and pgvector →

We use analytics cookies (Google Analytics, PostHog) to see which guides are useful. No ad networks, no cross-site tracking. See our privacy policy.