How to Self-Host Maddy Mail Server Using Docker Compose Behind Caddy
Maddy is a modern, all-in-one open-source mail server written in Go. Learn how to deploy Maddy with Docker Compose, leverage Caddy for automatic SSL certificates, and configure DKIM, SPF, and DMARC.

Operating an email server has historically required orchestrating multiple decoupled utilities: Postfix for message transfer (MTA), Dovecot for IMAP storage (MDA), OpenDKIM for signing, and OpenDMARC for policy enforcement. Each service relies on legacy configuration semantics, distinct log streams, and complex inter-process socket communication.
Maddy Mail Server consolidates the entire mail pipeline into a single, composable binary written in Go. It natively manages SMTP, IMAP4, DKIM signing, SPF verification, and DMARC enforcement using an embedded SQLite database by default.
This guide walks through deploying Maddy in Docker behind Caddy, configuring automatic TLS certificate synchronization, and provisioning user mailboxes.
Prerequisites
Ensure the host environment meets the following conditions before proceeding:
- A cloud server running Ubuntu 22.04/24.04 LTS (or Debian 12) with a dedicated public IPv4 address.
- Docker Engine and Docker Compose v2 installed.
- A domain name with access to edit DNS records (e.g.,
example.com). - An assigned Reverse DNS (PTR record) configured at your hosting provider pointing your IP to
mail.yourdomain.com.
Core Architecture & Features
Maddy’s internal pipeline processes inbound and outbound mail through modular message pipelines defined in a single configuration file (maddy.conf).
All-in-One Protocol Support
- SMTP & Submission: Inbound MX (port 25), Submission with STARTTLS (port 587), and Implicit TLS Submission (port 465).
- IMAP4rev1 Storage: Built-in IMAP server supporting STARTTLS (port 143) and Implicit TLS (port 993).
- Zero External Dependencies: Uses SQLite for credential storage, mail indexing, and delivery queues without requiring separate database containers.
Native Authentication & Delivery Controls
- Automated DKIM: Generates RSA/Ed25519 cryptographic keys on first boot and automatically signs outgoing messages.
- SPF Verification: Evaluates inbound sender IP authorizations against published domain policies.
- DMARC Enforcement: Built-in parser checks alignment and applies
none,quarantine, orrejectpolicies directly at the SMTP transaction stage.
Security & Isolation
- Runs as an unprivileged system user (
maddy:maddy, UID 1000) inside containerized namespaces. - Supports strict TLS configurations, ALPN challenge routing, and modern cipher suites.
Preparing the Deployment Environment
Step 1: Verify Inbound and Outbound Port 25
Residential ISPs and several cloud hosts (such as AWS, DigitalOcean, and Linode) block outbound traffic on TCP port 25 to mitigate spam abuse.
Run this check from your server CLI:
telnet gmail-smtp-in.l.google.com 25
If the connection is allowed, the remote server returns an SMTP handshake banner:
Trying 142.251.163.26...
Connected to gmail-smtp-in.l.google.com.
Escape character is '^]'.
220 mx.google.com ESMTP
Type QUIT to disconnect. If the connection stalls or drops with Connection timed out, request an unblock on outbound port 25 through your host provider’s support ticket system.
Step 2: Establish the Directory Layout
Create persistent directories to preserve database files, DKIM keys, and Caddy’s certificates across container restarts:
sudo mkdir -p /opt/maddy/{maddydata,caddydata,caddyconfig}
cd /opt/maddy
Required DNS Records
Before initiating certificate challenges, publish the necessary DNS records through your registrar or DNS provider (e.g., Cloudflare, Route53):
| Type | Name | Value | Priority / TTL |
|---|---|---|---|
| A | mail |
YOUR_SERVER_IPV4 |
Auto / 300s |
| MX | @ |
mail.yourdomain.com. |
Priority 10 |
| TXT | @ |
v=spf1 mx ~all |
Auto / 300s |
| TXT | _dmarc |
v=DMARC1; p=quarantine; adkim=r; aspf=r; |
Auto / 300s |
If you route web traffic through Cloudflare, set the Proxy status for the mail A-record to DNS Only (Grey Cloud). Cloudflare’s HTTP proxy does not proxy raw SMTP/IMAP traffic on standard mail ports.
Caddy Configuration
Caddy handles ACME HTTP-01 challenges and provisions wildcard or dedicated Let’s Encrypt certificates. By mounting Caddy’s data storage into the Maddy container, Maddy reads valid certificates directly without duplicate renewal scripts or certbot hooks.
Create /opt/maddy/Caddyfile:
sudo nano Caddyfile
Paste the following configuration, replacing the email and domain with your own:
{
email admin@yourdomain.com
}
mail.yourdomain.com {
# Handles HTTP-01 ACME challenge verification
respond "Maddy Mail Endpoint Active" 200
}
Docker Compose Stack
Maddy publishes standard mail ports directly on the host interface, while mounting Caddy’s storage volume as read-only.
Create /opt/maddy/docker-compose.yml:
sudo nano docker-compose.yml
Paste the following stack configuration:
version: "3.8"
services:
caddy:
image: caddy:2-alpine
container_name: caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- ./caddyconfig:/config
maddy:
image: foxcpp/maddy:latest
container_name: maddy
restart: unless-stopped
depends_on:
- caddy
ports:
- "25:25" # SMTP Inbound Transfer
- "143:143" # IMAP (STARTTLS)
- "465:465" # SMTPS (Implicit TLS Submission)
- "587:587" # Submission (STARTTLS)
- "993:993" # IMAPS (Implicit TLS)
environment:
- MADDY_HOSTNAME=mail.yourdomain.com
- MADDY_DOMAIN=yourdomain.com
volumes:
- ./maddydata:/data
- caddy_data:/caddy_certs:ro
volumes:
caddy_data:
Configuring Maddy
Start Caddy once to generate certificates and allow Maddy to write its baseline configuration:
docker compose up -d
Check Caddy logs to verify the certificate issued successfully:
docker compose logs caddy
Stop the stack to adjust maddy.conf:
docker compose down
Open /opt/maddy/maddydata/maddy.conf in your editor. Locate the primary tls directive and update the certificate paths to point to Caddy’s shared volume mount:
$(primary_domain) = yourdomain.com
$(local_domains) = $(primary_domain)
$(hostname) = mail.yourdomain.com
tls file /caddy_certs/caddy/certificates/acme-v02.api.letsencrypt.org-directory/[mail.yourdomain.com/mail.yourdomain.com.crt](https://mail.yourdomain.com/mail.yourdomain.com.crt) /caddy_certs/caddy/certificates/acme-v02.api.letsencrypt.org-directory/[mail.yourdomain.com/mail.yourdomain.com.key](https://mail.yourdomain.com/mail.yourdomain.com.key) {
protocols tls1.2 tls1.3
}
Relaunch the entire mail stack:
docker compose up -d
Publishing the DKIM Record
During initial startup, Maddy automatically generates an RSA DKIM key located in /opt/maddy/maddydata/dkim_keys/.
Display the generated public DNS record:
cat /opt/maddy/maddydata/dkim_keys/yourdomain.com_default.dns
The output provides the TXT record format required by your DNS manager:
v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0r1...
Add this as a TXT record in your DNS console:
- Name:
default._domainkey - Value: Paste the full string output by Maddy.
Mailbox Provisioning
Account management is executed directly via Maddy’s CLI within the running container.
Step 1: Create Account Authentication Credentials
docker exec -it maddy maddy creds create admin@yourdomain.com
Prompt: Enter a secure password and confirm.
Step 2: Initialize IMAP Mailbox Storage
docker exec -it maddy maddy imap-acct create admin@yourdomain.com
Step 3: Verify Provisioned Credentials
docker exec -it maddy maddy creds list
Client Configuration Details
Use the following parameters to connect desktop and mobile mail clients (Thunderbird, Apple Mail, Outlook):
Incoming Mail (IMAP)
- Server:
mail.yourdomain.com - Port:
993 - Security: SSL/TLS (Implicit TLS)
- Username:
admin@yourdomain.com - Authentication: Normal Password
Outgoing Mail (SMTP)
- Server:
mail.yourdomain.com - Port:
465(Implicit TLS) or587(STARTTLS) - Security: SSL/TLS or STARTTLS
- Username:
admin@yourdomain.com - Authentication: Normal Password
Verification and Delivery Health
Verify inbound and outbound delivery health by sending a test message to mail-tester.com.
Inspect active mail transaction logs in real time:
docker compose logs -f maddy
A properly configured server will report passing checks across SPF, DKIM, and DMARC alignment, establishing high deliverability for outbound emails.
Continue reading

How to Self-Host Stalwart Mail Server Using Docker Compose Behind Caddy v2.8.4
Stalwart is an open-source mail server solution with JMAP, IMAP4, POP3 & SMTP support. It is written in Rust and aims to be secure, fast, robust, and scalable.

How to Deploy Litestream Using Docker Compose Behind Caddy
Litestream is a powerful, standalone streaming replication tool for SQLite. Learn how to deploy it alongside your web applications using Docker Compose and a Caddy reverse proxy.

How to Deploy Authelia Using Docker Compose Behind Caddy
Authelia is an open-source authentication and authorization server providing two-factor authentication and single sign-on (SSO) for applications via a web portal.