Running Docker Mailserver yourself | a field report
With the help of a suitable Docker image, it is relatively easy to run a mail server yourself. I originally used the integrated mail server of the Host Europe vServer (Plesk) and came across a very simple Docker container while looking for a replacement. The lightweight container provides a mail server without a graphical management interface, but can be managed with a few simple commands. Any email client can be used to send and receive the mails, for this POP3 or IMAP is offered for receiving and SMTP for sending. Whether the mails of the own server can be received and delivered, depends on the setup, on the correct DNS settings.
Docker Basics
Software Docker-mailserver GitHub https://github.com/docker-mailserver/docker-mailserver current version 14.0.0
found 2024-06-10
A container is an isolated environment independent of the operating system (OS):
When a container is first launched, Docker independently loads all the necessary sources
from the internet.
Docker can be installed on Windows, macOS or an Linux Distribution
Prerequisite
In advance, I installed Docker, see: topic/docker and for the SSL certificates I have a reverse proxy in use: traefik-reverse-proxy.
Mail server commissioning
I used the following Docker image as my mail server: "ghcr.io/docker-mailserver/docker-mailserver:latest".
There are 3 files required to get started:
- docker-compose.yml
- .env (The variables of the .env file could also be replaced directly in the docker-compose.yml file)
- .mailserver.env (variables for the mail server settings).
I created the docker-compose file as follows:
services:
mailserver:
image: docker.io/mailserver/docker-mailserver:latest
hostname: ${HOSTNAME}
domainname: ${DOMAINNAME}
container_name: ${CONTAINER_NAME}
env_file: mailserver.env
ports:
- "25:25"
- "143:143"
- "587:587"
- "993:993"
- "4190:4190"
volumes:
- ./docker-data/dms/maildata:/var/mail
- ./docker-data/dms/mailstate:/var/mail-state
- ./docker-data/dms/maillogs:/var/log/mail
- ./docker-data/dms/config/:/tmp/docker-mailserver/
- ./docker-data/dms/cron/sa-learn:/etc/cron.d/sa-learn
- /etc/localtime:/etc/localtime:ro
restart: always
cap_add: [ "NET_ADMIN", "SYS_PTRACE" ]
In addition, I use a separate file for the variables in the docker-compose.yml file: .env, this contains the values for the host name, domain name and container name:
HOSTNAME=mailserver.domain.tld
DOMAINNAME=domain.tld
CONTAINER_NAME=mailserver
And lastly, the mailserver.env file is needed for the setup, this can be downloaded from the git repository:
wget -O mailserver2.env https://raw.githubusercontent.com/docker-mailserver/docker-mailserver/master/mailserver.env
With this, the server would basically be ready for use and could already be started. Could be, because for additional settings the file mailserver.env should also be briefly reviewed and features required therein should be activated or deactivated. The file is relatively well commented and thus largely self-explanatory. In order for the mails to be delivered and received, the corresponding DNS records must also be created.
The container is then started with the command "docker compose up".
root@l2:~/mailserver# docker compose up -d
...
Status: Downloaded newer image for mailserver/docker-mailserver:latest
Creating mailserver ... done
Attaching to mailserver
mailserver.env
I have adjusted the following settings in the mailserver.env file:
OVERRIDE_HOSTNAME=domain.tld
POSTMASTER_ADDRESS=postmaster@domain.tld
ENABLE_FAIL2BAN=1
ENABLE_MANAGESIEVE=1
SSL_TYPE=letsencrypt
REPORT_RECIPIENT=1
ENABLE_SPAMASSASSIN=1
SRS_SENDER_CLASSES=envelope_sender,header_sender
ONE_DIR=1
POSTFIX_MESSAGE_SIZE_LIMIT=300000000
For SSL_TYPE=letsencrypt, see: ssl
For ENABLE_MANAGESIEVE=1, see: SIEVE
For ENABLE_SPAMASSASSIN, see SpamAssassin
By default the size of the mails is limited to 10MB, by setting the variable “POSTFIX_MESSAGE_SIZE_LIMIT” the limit can be increased.
Administration
New email addresses or forwardings can be created using the setup script. Since version 10.2 the script is located in the Docker container, accordingly the call is done via “docker exec”. The help can be displayed as follows:
docker exec mailserver setup help
“mailserver” in the command stands for the name of the Docker container, here is the displayed help for version 11.2
root@ubuntu:/var/mailserver# docker exec mailserver setup help
SETUP(1)
NAME
setup - 'docker-mailserver' Administration & Configuration script
SYNOPSIS
./setup [ OPTIONS... ] COMMAND [ help | ARGUMENTS... ]
COMMAND := { email | alias | quota | dovecot-master | config | relay | debug } SUBCOMMAND
DESCRIPTION
This is the main administration script that you use for all your interactions with
'docker-mailserver'. Setup, configuration and much more is done with this script.
Please note that this script executes most of its commands inside the container itself.
If it cannot find a running 'docker-mailserver' container, it will attempt to run one using
any available tags which include 'label=org.opencontainers.image.title="docker-mailserver"'
and then run the necessary commands. If the tag for the container is not found, this script
will pull the ':latest' tag of 'docker.io/mailserver/docker-mailserver'.
This tag refers to the latest release, see the tagging convention in the README under:
https://github.com/docker-mailserver/docker-mailserver/blob/master/README.md
You will be able to see detailed information about the script you're invoking and their
arguments by appending 'help' after your command. Currently, this does not work with all scripts.
[SUB]COMMANDS
COMMAND email :=
/usr/local/bin/setup email add <EMAIL ADDRESS> [<PASSWORD>]
/usr/local/bin/setup email update <EMAIL ADDRESS> [<PASSWORD>]
/usr/local/bin/setup email del [ OPTIONS... ] <EMAIL ADDRESS> [ <EMAIL ADDRESS>... ]
/usr/local/bin/setup email restrict <add|del|list> <send|receive> [<EMAIL ADDRESS>]
/usr/local/bin/setup email list
COMMAND alias :=
/usr/local/bin/setup alias add <EMAIL ADDRESS> <RECIPIENT>
/usr/local/bin/setup alias del <EMAIL ADDRESS> <RECIPIENT>
/usr/local/bin/setup alias list
COMMAND quota :=
/usr/local/bin/setup quota set <EMAIL ADDRESS> [<QUOTA>]
/usr/local/bin/setup quota del <EMAIL ADDRESS>
COMMAND dovecot-master :=
/usr/local/bin/setup dovecot-master add <USERNAME> [<PASSWORD>]
/usr/local/bin/setup dovecot-master update <USERNAME> [<PASSWORD>]
/usr/local/bin/setup dovecot-master del [ OPTIONS... ] <USERNAME> [ <USERNAME>... ]
/usr/local/bin/setup dovecot-master list
COMMAND config :=
/usr/local/bin/setup config dkim [ ARGUMENTS... ]
COMMAND relay :=
/usr/local/bin/setup relay add-auth <DOMAIN> <USERNAME> [<PASSWORD>]
/usr/local/bin/setup relay add-domain <DOMAIN> <HOST> [<PORT>]
/usr/local/bin/setup relay exclude-domain <DOMAIN>
COMMAND fail2ban :=
/usr/local/bin/setup fail2ban
/usr/local/bin/setup fail2ban ban <IP>
/usr/local/bin/setup fail2ban unban <IP>
COMMAND debug :=
/usr/local/bin/setup debug fetchmail
/usr/local/bin/setup debug login <COMMANDS>
/usr/local/bin/setup debug show-mail-logs
EXAMPLES
./setup.sh email add test@example.com
Add the email account test@example.com. You will be prompted
to input a password afterwards since no password was supplied.
./setup.sh config dkim keysize 2048 domain 'example.com,not-example.com'
Creates keys of length 2048 but in an LDAP setup where domains are not known to
Postfix by default, so you need to provide them yourself in a comma-separated list.
./setup.sh config dkim help
This will provide you with a detailed explanation on how to use the
config dkim command, showing what arguments can be passed and what they do.
Create mail addresses
docker exec mailserver setup email add email@domain password
After the first creation of an email address for a new domain, the DKIM key for the DNS record can be created, see DKIM.
Create alias (forwarding)
docker exec mailserver setup alias add email@domain.tld weiterleitung@mail-adresse.ok
DKIM
After the first mail addresses or forwardings have been created on the system, DKIM Keys can be generated with the following command:
docker exec mailserver setup config dkim
The keys are stored for all domains for which an email address or forwarding was created in the following folder: config/opendkim/keys/domain.tld/mail.txt. The keys should then be used for the DNS entry:
DNS entries
For the operation of a mail server, the DKIM key, an SPF record and a DMARC policy should be created in the DNS in addition to the A and MX record. The domain name: domain.tld is representative for the own domain in the following examples. The following entries must be added to the DNS zone for domain.tld:
A-Record
Type | Name | Value |
---|---|---|
A | mail.domain.tld | IP address of the web server |
In addition, mail.domain.tld should be stored as the server name in the reverse lookup zone so that the IP address of the web server also resolves to mail.domain.tld. For a vServer or cloud server of a provider, the server name can be set in the admin interface of the provider.
MX
Type | Name | Value | Priorität |
---|---|---|---|
MX | mail.domain.tld | IP address of the web server |
The MX record is responsible for delivering the mails. All mails that have @domain.tld as domain will be delivered to this server. If there are several mail servers, the server with the lowest priority is used.
The previously created DKIM Key:
Type | Name | Value |
---|---|---|
TXT | mail._domainkey.domain.tld | v=DKIM1; h=sha256; k=rsa; p=Content-From-config/opendkim/keys/domain.tld/mail.txt without " |
DKIM (DomainKeys Identified Mail) is a signature that is added to mails to make it more difficult to forge the sender. DKIM is used for sending mails. If the provider only supports 512 characters for its DNS entries, a shorter key can be generated with the following call: "docker exec mailserver setup config dkim keysize 2048". For the keys to be reissued, the old keys in the folder ..config/opendkim/keys/domain.tld must be deleted beforehand and the mail server restarted.
SPF-Eintrag
Type | Name | Value |
---|---|---|
TXT | @ | “v=spf1 ip4:IP-Adresse-des-Webservers -all" |
To use the ip4 setting of another domain, the SPF entry can be redirected as follows. It is important that the -all parameter is not used:
Type | Name | Value |
---|---|---|
TXT | @ | “v=spf1 redirect=server.domain.tld" |
The SPF entry (Sender Policy Framework) is also a measure to make it more difficult to forge email addresses. The SPF entry specifies which servers are authorized to send the emails, in the example the IP address of our mail server.
DMARC
Type | Name | Value |
---|---|---|
TXT | _dmarc.domain.tld | “v=DMARC1; p=reject; rua=mailto:report@domain.tld" |
DMARC builds on SPF and DKIM and specifies how the server to which mail is sent should authenticate the email.
Check DNS
The easiest way to test the DNS records is on the mxtoolbox.com site: mxtoolbox.com/MXLookup.aspx
As an example for the domain: domain.tld
MX Lookup
The MX lookup on the mxtoolbox page should look something like this:
Pref | Hostname | IP Address | TTL | |
---|---|---|---|---|
10 | mail.domain.tld | ???.???.???.??? | 24 hrs | Blacklist Check SMTP Test |
Test | Result | |
---|---|---|
✓ | DMARC Record Published | DMARC Record found |
✓ | DMARC Policy Not Enabled | DMARC Quarantine/Reject policy enabled |
✓ | DNS Record Published | DNS Record found |
SMTP Test
Here is an SMTP test on mxtoolbox:
smtp:IP addressMailserver
220 mail.domain.tld ESMTP
Test | Result | |
---|---|---|
✓ | SMTP Reverse DNS Mismatch | OK - ???.???.???.??? resolves to domain.tld |
✓ | SMTP Valid Hostname | OK - Reverse DNS is a valid Hostname |
✓ | SMTP Banner Check | OK - Reverse DNS matches SMTP Banner |
✓ | SMTP TLS | OK - Supports TLS. |
✓ | SMTP Connection Time | 0.303 seconds - Good on Connection time |
✓ | SMTP Open Relay | OK - Not an open relay. |
✓ | SMTP Transaction Time | 1.006 seconds - Good on Transaction Time |
Email to a Google address
As an example, the email from your own web server can also be sent to a Google account:
Here, too, a status for SPF, DKIM and DMARC then appears under “Show original”:
Original Message
Message ID | |
---|---|
Created at: | Thu, Apr 8, 2021 at 06:55 AM (Delivered after 2 seconds) |
From: | First name Last name <user@domain.tld> |
To: | “Firstname Lastname (gmailuser@gmail.com)” <gmailuser@gmail.com> |
Subject: | test - dns |
SPF: | PASS with IP ???.???.???.??? |
DKIM: | 'PASS' with domain domain.tld |
DMARC: | 'PASS' |
SSL Certificates
As setup for SSL certificates I use Let's Encrypt and a reverse proxy, see: Traefik-Reverse-Proxy. To use the certificate management from the reverse proxy, I modified the docker-compose.yml file as follows:
services:
mailserver:
image: docker.io/mailserver/docker-mailserver:latest
hostname: ${HOSTNAME}
domainname: ${DOMAINNAME}
container_name: ${CONTAINER_NAME}
env_file: mailserver.env
ports:
- "25:25"
- "143:143"
- "587:587"
- "993:993"
- "4190:4190"
volumes:
- ./docker-data/dms/maildata:/var/mail
- ./docker-data/dms/mailstate:/var/mail-state
- ./docker-data/dms/maillogs:/var/log/mail
- ./docker-data/dms/config/:/tmp/docker-mailserver/
- ./docker-data/dms/cron/sa-learn:/etc/cron.d/sa-learn
- ../traefik/letsencrypt/acme.json:/etc/letsencrypt/acme.json:ro
- /etc/localtime:/etc/localtime:ro
restart: always
cap_add: [ "NET_ADMIN", "SYS_PTRACE" ]
cert-companion:
image: nginx
labels:
- "traefik.enable=true"
- "traefik.http.routers.mailserver.rule=Host(`${HOSTNAME}`)"
- "traefik.http.routers.mailserver.entrypoints=web"
- "traefik.http.routers.mailserver.entrypoints=websecure"
- "traefik.http.routers.mailserver.tls.certresolver=myresolver"
networks:
- proxy-tier
restart: always
networks:
proxy-tier:
external: true
name: webproxy
The path to the nginx-data folder of the web proxy under “Volumes” must of course be adjusted accordingly. In addition, unlike the original docker-compose file, I have redirected the other volumes to the file system. See: Docker data storage: Docker Volumes vs. Bind Mounts
SpamAssassin Spamfilter learn
To cope with the flood of spam mails, I trained SpamAssassin with the sa-learn command: SpamAssassin can learn from existing mails from the inbox and the junk folder besides blacklists and other spam checks. The sa-learn command examines both folders for this purpose and remembers certain patterns of spam and non-spam mails (ham). So if a mail is detected wrong and then moved to the spam folder by the mail client, SpamAssassin can learn from it and put a similar mail in the spam folder next time.
The sa-learn command can be launched in the Docker container as follows:
root@server:~# docker exec mailserver sa-learn --ham /var/mail/*/*/cur* --dbpath /var/mail-state/lib-amavis/.spamassassin
Learned tokens from 0 message(s) (1375 message(s) examined)
root@server:~# docker exec mailserver sa-learn --spam /var/mail/*/*/.Junk --dbpath /var/mail-state/lib-amavis/.spamassassin
Learned tokens from 22 message(s) (705 message(s) examined)
/var/*/*/cur* searches all inboxes and their subfolders, /var/mail/*/*/.Junk all spam folders of all mailboxes.
In order to start the learning command regularly, the cron service integrated in the container can be used and the following file can be created for this purpose:
docker-data/dms/cron/sa-learn
0 2 * * * root sa-learn --ham /var/mail/*/*/cur* --dbpath /var/mail-state/lib-amavis/.spamassassin
0 3 * * * root sa-learn --spam /var/mail/*/*/.Junk --dbpath /var/mail-state/lib-amavis/.spamassassin
SIEVE - Filter rules
With the help of Sieve filters it is possible to apply certain rules to the inbox, e.g. move certain senders to a folder. Here is an example of my filter rules used in Nextcloud:
require ["fileinto", "mailbox","envelope","reject"];
if anyof (address :contains "From" [
"@spamdomain1.tld",
"@spamdomain2.tld"
],
header :contains "Subject" [
"Viagra",
"other SPAM-Keyword",
]
)
{
fileinto :create "Junk";
}
elsif anyof (address :matches "From" [
"noreply@domain.tld",
"newsletter@domain.tld"
]
)
{
fileinto :create "Newsletter";
}
In order for Sieve to be changed via the filter rules using the mail client, port 4190 must be stored in docker-compose.
Fetch Mail - Get mails from other accounts
To regularly fetch emails from another account, “ENABLE_FETCHMAIL” can be enabled in the mailserver.env file:
# –––––––––––––––––––––––––––––––––––––––––––––––
# ––– Fetchmail Section –––––––––––––––––––––––––
# –––––––––––––––––––––––––––––––––––––––––––––––
ENABLE_FETCHMAIL=1
# The interval to fetch mail in seconds
FETCHMAIL_POLL=300
The default value for fetching is every 5 minutes.
The account data for fetching can then be stored in the following config file: docker-data/dms/config/fetchmail.cf
## Example configuration: IMAP
poll imap.domain.tld with proto IMAP
user 'ccc@gmx.at' there with
password 'pwd'
is 'mailboxOnDockerMailserver@domain.tld'
here ssl
Debug - Fail2ban
If the Fail2ban service is enabled, failed login attempts will be penalized by blocking the sender for some time. The blocked IP addresses can be displayed and unblocked as follows:
docker exec mailserver setup fail2ban
Banned in dovecot: ???.???.???.???
Banned in postfix-sasl: 60.171.17.185, 61.182.227.245, 117.50.65.112, 116.196.112.146, 119.62.142.225, 113.31.104.89
root@ubuntu-4gb-nbg1-2:/var/web/mailserver# ./setup.sh debug fail2ban unban ???.???.???.???
Unbanned IP from dovecot: ???.???.???.???
root@ubuntu:/var/web/mailserver#
Log output
As for other Docker containers, docker logs can be used to display what the container is currently doing:
docker logs -f mailserver
Troubleshooting: DNS records
Policy Reasons
Mail Delivery Subsystem
Returned mail: see transcript for details
This message was created automatically by mail delivery software.
Deny to deliver the message you sent to one or more recipients.
Reasons for deny are as follows:
REASONS: Policy Reasons
RECIPIENTS:
???@???.xx
Possible cause: missing SPF record
Domain unknown
An email to an unknown domain is presented approximately like this by the mail delivery subsystem:
Mail Delivery Subsystem
Returned mail: see transcript for details
Undelivered Mail Returned to Sender
This is the mail system at host mail.domain.tld.
I'm sorry to have to inform you that your message could not
be delivered to one or more recipients. It's attached below.
For further assistance, please send mail to postmaster.
If you do so, please include this problem report. You can
delete your own text from the attached returned message.
The mail system
<not-available@invalid.domain
Quoted text
>: Host or domain name not found. Name service error
for name=invalid.domain type=AAAA: Host not found
Delivery report
postmaster@???.???.???
Delivery report
Hello, this is the mail server on ???.???.???
I am sending you this message to inform you on the delivery status of a
message you previously sent. Immediately below you will find a list of
the affected recipients; also attached is a Delivery Status Notification
(DSN) report in standard format, as well as the headers of the original
message.
<user@???.???.???
Quoted text
> delivery failed; will not continue trying
Upgrade
As usual for Docker, new versions can be loaded with a pull:
docker-compose pull
docker compose up -d
Conclusion
Admittedly, administration via commands in the terminal is certainly not everyone's cup of tea. However, the advantage of this setup is that the configuration and the data of the complete mail server are located in one folder thanks to Docker. And the server can thus be copied to another server very easily and started there, see also: topic/docker
{{percentage}} % positive