How to Self-Host Maddy Mail Server with Docker

What Is Maddy?

Maddy is a self-hosted, all-in-one email server that replaces Postfix + Dovecot + OpenDKIM + rspamd with a single binary. It handles SMTP (sending and receiving), IMAP (mailbox access), DKIM signing, SPF/DMARC verification, and MTA-STS — all in one process. Written in Go, it uses minimal resources compared to traditional multi-component email stacks. Maddy replaces Gmail, Outlook, and Proton Mail.

Prerequisites

  • A Linux server (Ubuntu 22.04+ recommended)
  • Docker and Docker Compose installed (guide)
  • 256 MB of free RAM
  • 1 GB of free disk space (plus storage for mailboxes)
  • A domain name with full DNS control
  • Port 25 open (check with your VPS provider — some block it)
  • A static IP address with PTR record configured

Docker Compose Configuration

Create a docker-compose.yml file:

services:
  maddy:
    image: foxcpp/maddy:0.8.2
    container_name: maddy
    restart: unless-stopped
    hostname: mail.example.com
    ports:
      - "25:25"       # SMTP (incoming mail)
      - "465:465"     # SMTPS (submission over TLS)
      - "587:587"     # SMTP submission (STARTTLS)
      - "993:993"     # IMAPS (mailbox access)
    environment:
      MADDY_HOSTNAME: mail.example.com
      MADDY_DOMAIN: example.com
    volumes:
      - maddy_data:/data        # Mailboxes, DKIM keys, databases
      - maddy_config:/etc/maddy # Configuration files
      - /etc/letsencrypt:/etc/letsencrypt:ro  # TLS certificates
    healthcheck:
      test: ["CMD", "maddy", "version"]
      interval: 60s
      timeout: 5s
      retries: 3

volumes:
  maddy_data:
  maddy_config:

Important: Replace mail.example.com with your actual mail hostname and example.com with your domain.

Start the stack:

docker compose up -d

Initial Setup

1. Configure maddy.conf

The default configuration works for most setups, but you need to set your hostname and domain. Edit the config inside the container:

docker compose exec maddy sh -c 'cat /etc/maddy/maddy.conf' | head -10

The key settings in maddy.conf:

$(hostname) = mail.example.com
$(primary_domain) = example.com
$(local_domains) = $(primary_domain)

If you need to modify the config, mount it as a bind mount instead of a named volume:

volumes:
  - ./maddy.conf:/etc/maddy/maddy.conf:ro
  - maddy_data:/data
  - /etc/letsencrypt:/etc/letsencrypt:ro

2. TLS Certificates

Maddy requires valid TLS certificates. The recommended approach is using Let’s Encrypt with certbot on the host:

sudo apt install certbot
sudo certbot certonly --standalone -d mail.example.com

The certificates are mounted into the container via the /etc/letsencrypt volume. Update maddy.conf to point to them:

tls file /etc/letsencrypt/live/mail.example.com/fullchain.pem /etc/letsencrypt/live/mail.example.com/privkey.pem

3. Create User Accounts

Maddy manages users via CLI commands:

# Create authentication credentials
docker compose exec maddy maddy creds create [email protected]

# Create IMAP mailbox storage
docker compose exec maddy maddy imap-acct create [email protected]

You’ll be prompted for a password. The user can then connect with any IMAP client.

4. DNS Records

Set up all required DNS records. Maddy auto-generates DKIM keys on first start — retrieve them:

docker compose exec maddy maddy dkim ls

Add these records to your domain’s DNS:

RecordTypeNameValue
MXMXexample.com10 mail.example.com
AAmail.example.comYour server IP
SPFTXTexample.comv=spf1 ip4:YOUR_IP -all
DKIMTXTdefault._domainkey.example.comFrom maddy dkim ls output
DMARCTXT_dmarc.example.comv=DMARC1; p=quarantine; rua=mailto:[email protected]
PTRPTRYour IPmail.example.com (set at VPS provider)

See the Email Deliverability guide for detailed explanations of each record.

Configuration

Multiple Domains

Add additional domains to $(local_domains):

$(local_domains) = $(primary_domain) seconddomain.com thirddomain.net

Each domain needs its own MX, SPF, DKIM, and DMARC records.

Storage Backend

Maddy uses an embedded database (SQLite) by default for mailbox storage. For larger deployments, it supports PostgreSQL:

storage.imapsql local_mailboxes {
    driver postgres
    dsn "host=db user=maddy password=secret dbname=maddy"
}

Spam Filtering

Maddy includes basic spam filtering via DNS blacklists (DNSBLs) and SPF/DKIM/DMARC checks. Configure additional DNSBLs in maddy.conf:

check {
    dnsbl {
        reject_threshold 3
        zen.spamhaus.org
        bl.spamcop.net
    }
    spf
    dkim
    dmarc
}

Rate Limiting

Protect against brute-force and abuse:

limits {
    all rate 20 1s    # 20 connections per second globally
    ip rate 5 1s      # 5 connections per second per IP
}

Reverse Proxy

Maddy handles TLS itself — you typically don’t put a reverse proxy in front of mail ports. However, if you want to access the webmail (not included — Maddy is server-only) via a reverse proxy, use a separate webmail client like Roundcube or Snappymail.

For mail ports, expose them directly. See Reverse Proxy Setup for web UI proxying.

Backup

Back up the data volume which contains mailboxes, DKIM keys, and authentication databases:

docker run --rm -v maddy_data:/data -v $(pwd):/backup alpine \
  tar czf /backup/maddy-backup-$(date +%Y%m%d).tar.gz /data

Critical files inside /data:

  • dkim_keys/ — DKIM private keys (losing these means regenerating keys and updating DNS)
  • imapsql.db — Mailbox database
  • credentials.db — User authentication database

See Backup Strategy for a complete backup approach.

Troubleshooting

Cannot Connect via IMAP Client

Symptom: Thunderbird or other IMAP clients can’t connect. Fix: Ensure port 993 (IMAPS) is open in your firewall. Use the correct settings:

  • Server: mail.example.com
  • Port: 993
  • Security: SSL/TLS
  • Authentication: Normal password

Outgoing Mail Lands in Spam

Symptom: Emails you send arrive in recipients’ spam folders. Fix: Verify all DNS records (SPF, DKIM, DMARC, PTR). Test with mail-tester.com. See Self-Hosted Email Going to Spam for a comprehensive fix guide.

”certificate verify failed” in Logs

Symptom: TLS errors in maddy logs. Fix: Check that your Let’s Encrypt certificate is valid and the paths in maddy.conf are correct. Renew with certbot renew if expired. The maddy container needs read access to the certificate files.

Port 25 Blocked

Symptom: You can send mail but can’t receive. Fix: Many VPS providers block port 25. Check with your provider. See the provider compatibility table for details.

Resource Requirements

  • RAM: ~50 MB idle, 100-150 MB under moderate load
  • CPU: Very low — single Go binary
  • Disk: ~30 MB for the application, plus mailbox storage

Maddy is significantly lighter than mailcow (~1.5 GB RAM) or Mailu (~512 MB RAM). It’s the most resource-efficient self-hosted email option.

Verdict

Maddy is the best choice for self-hosted email if you want minimal resource usage and a simple setup. A single binary replaces the 6+ components that traditional email stacks require. The trade-off: no web UI (you need a separate webmail client like Roundcube), no admin dashboard (manage via CLI), and a smaller community compared to mailcow. Choose maddy for a lightweight, technically elegant email server. Choose mailcow if you want a batteries-included solution with webmail and admin UI. Choose Mail-in-a-Box if you want the simplest possible setup.

Comments