Featured image of post Setting Up a Docker-Mailserver

Setting Up a Docker-Mailserver

Getting Caddy to request TLS certificates as required is a real hassle.

One morning, I woke up to an email notifying me that someone had logged into my account from another country. At first, I assumed it was just a routine IP error and didn’t give it much thought.

But over the next few days, new emails kept coming in sporadically, each time from a different IP. This became a problem. If I didn’t take action soon, my mailbox might really be hijacked for mass emailing. So, I quickly deleted the MX, SPF, and DMARC records of the domain and sought a new solution.

I originally just wanted to change the password, but the entry point for changing the password in Tencent Enterprise Mail is incredibly well-hidden. I couldn’t find it anywhere. Forget it, I’ll migrate. No need to put up with this.

Pull Configuration Files

Docker-mailserver requires two configuration files, compose.yaml and mailserver.env. Pull the configuration files as follows:

1
2
3
4
mkdir mailserver && cd mailserver
sudo apt install wget
wget -O compose.yaml "https://raw.githubusercontent.com/docker-mailserver/docker-mailserver/master/compose.yaml"
wget -O mailserver.env "https://github.com/docker-mailserver/docker-mailserver/blob/master/mailserver.env"

You can modify the configurations as needed. Here are the parts I modified:

1
2
3
4
5
POSTMASTER_ADDRESS=webmaster@l3zc.com
TZ=Asia/Shanghai
SSL_TYPE=manual
SSL_CERT_PATH=/certs/cert.crt
SSL_KEY_PATH=/certs/cert.key

Using Caddy to Maintain TLS Certificates1

Since I was already using Caddy, I decided to use it to automatically maintain the certificates. According to the project’s official documentation, I just needed to add a subdomain in the Caddyfile, and Caddy would automatically maintain the certificates for me.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
mail.example.com {
  tls internal {
    # ?? This is just generating an internal certificate
    key_type rsa2048
  }

  # Optional, can be useful for troubleshooting
  # connection to Caddy with the correct certificate:
  respond "Hello DMS"
}

After updating the DNS records, I found that my situation was a bit special: my server didn’t use Caddy’s automatic certificate requesting feature but instead used a 15-year wildcard Cloudflare Origin Certificate. Even if I didn’t specify a certificate file for this subdomain, Caddy would load the wildcard certificate and think there’s no need to request a new one. This was quite awkward. With Caddy, it was either the Cloudflare Origin Certificate or the mail server. I had to install dns.providers.cloudflare, so everything would be automated, out of sight, out of mind.

Global configuration after installing dns.providers.cloudflare:

1
2
3
{
        acme_dns cloudflare {API_KEY}
}

Finally, map Caddy’s certificate storage location to the Caddy container.

1
2
volumes:
   - /home/user/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/yourdomain.tld/:/certs/

If Caddy runs as the root user, the certificate location should be /root/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/yourdomain.tld/. Make the corresponding changes.

Then, as mentioned before, I mapped the certificate storage location to the /certs/ directory in the container, so the configuration is:

1
2
SSL_CERT_PATH=/certs/cert.crt
SSL_KEY_PATH=/certs/cert.key

Firewall Configuration

My firewall is a bit special, using ufw and the configuration mentioned in To Fix The Docker and UFW Security Flaw Without Disabling Iptables. So when I want to expose Docker container ports to the outside, I need to make additional configurations.

1
2
3
4
5
6
# Open the ports needed for the mail server
ufw route allow proto tcp from any to any port 25
ufw route allow proto tcp from any to any port 143
ufw route allow proto tcp from any to any port 465
ufw route allow proto tcp from any to any port 587
ufw route allow proto tcp from any to any port 993

First Time Starting the Container

The first time you start the container, you need to create a new Postmaster account immediately (within 120 seconds).

1
2
docker compose up -d
docker exec -it <CONTAINER NAME> setup email add <postmaster@yourdomain.tld>

Then you can stop the container and continue configuring.

1
docker compose down

DNS Records

  • An A record, with any name (assume mail), pointing to the server IP (Cloudflare requires “DNS only”).
  • An MX record, Apex domain (@), pointing to mail.yourdomain.tld.
  • A TXT record (SPF), Apex domain (@), which can be generated here (optional).
  • A TXT record (DMARC), name _dmarc, which can be generated here (optional).
  • A TXT record (DKIM), name mail._domainkey, specific configuration provided later in the text (optional).

DKIM, SPF, and DMARC2

What are DKIM, SPF, and DMARC? Simply put, DKIM is a digital signature mechanism; SPF is similar to an “employee list,” recording all legitimate sending servers; and DMARC is used to instruct the recipient’s server on how to handle emails that fail DKIM and SPF checks. 3

First, set up DKIM by generating a public/private key pair on the server.

1
docker exec -it <CONTAINER NAME> setup config dkim

The generated key pair can be found in the mapped container directory ./docker-data/dms/config/opendkim/keys/.

mail.txt is the DNS Zone file containing the public key Cloudflare’s import section

Download the mail.txt (or copy all its contents and paste into a local file), and import it into the Cloudflare Dashboard to complete the DKIM setup. Note that after configuring DKIM, you need to restart the container for the changes to take effect. 4

1
docker compose up -d --force-recreate

As mentioned earlier, SPF is like an “employee list.” Since I only have one legitimate sending server, the DNS should be configured as follows:

1
"v=spf1 mx ~all"

Lastly, for DMARC, you can use the template mentioned earlier, so I won’t be repeated here.

If your domain provider has its own setup wizard (such as Cloudflare), you can also use it to simplify the setup process.

Client Configuration

Client configuration is relatively simple. Both the IMAP and SMTP servers are mail.yourdomain.ltd. After enabling SSL, the ports are 465 and 993, respectively. Just log in.

Configuration example

TODO-List

  • Add an WebUI
Built with Hugo
Theme Stack designed by Jimmy