Docker Mailserver Setup: 7 Easy Steps and 5 Mistakes
Docker Mailserver (the docker-mailserver project, DMS) is a complete mail server in one container: Postfix for SMTP, Dovecot for IMAP, OpenDKIM, Amavis and optional spam and virus filters, configured through one environment file and a setup command. This guide sets up Docker Mailserver 16.0.1 with the project's own compose.yaml, creates accounts and an alias, sends and reads real mail, and generates DKIM keys. Every output below comes from a test run on 17 October 2026 (podman 5.8 with podman-compose on a Fedora host as the engine; the docker commands are identical), including five errors you are likely to meet.
curl -sO https://raw.githubusercontent.com/docker-mailserver/docker-mailserver/v16.0.1/compose.yaml
curl -sO https://raw.githubusercontent.com/docker-mailserver/docker-mailserver/v16.0.1/mailserver.env
docker compose up -d
docker exec -it mailserver setup email add admin@example.com # within 120 s of the first start
docker exec mailserver setup config dkim # DKIM key + DNS record
docker exec mailserver setup email list
Contents
Docker Mailserver files: compose.yaml and mailserver.env
A Docker Mailserver install is two files. The upstream compose.yaml defines one service, mailserver, with five ports and four data directories:
services:
mailserver:
image: ghcr.io/docker-mailserver/docker-mailserver:latest
container_name: mailserver
# Provide the FQDN of your mail server here (Your DNS MX record should point to this value)
hostname: mail.example.com
env_file: mailserver.env
ports:
- "25:25" # SMTP (explicit TLS => STARTTLS, Authentication is DISABLED => use port 465/587 instead)
- "143:143" # IMAP4 (explicit TLS => STARTTLS)
- "465:465" # ESMTP (implicit TLS)
- "587:587" # ESMTP (explicit TLS => STARTTLS)
- "993:993" # IMAP4 (implicit TLS)
volumes:
- ./docker-data/dms/mail-data/:/var/mail/
- ./docker-data/dms/mail-state/:/var/mail-state/
- ./docker-data/dms/mail-logs/:/var/log/mail/
- ./docker-data/dms/config/:/tmp/docker-mailserver/
- /etc/localtime:/etc/localtime:ro
restart: always
stop_grace_period: 1m
Set hostname to the name your MX record points to. For the test the image was pinned to 16.0.1 instead of latest (do that in production too, so an update never surprises you), the hostname was mail.example.test, and a second container on the same compose network acted as the mail client. mailserver.env is 660 lines of documented options; the defaults that matter first:
71:PERMIT_DOCKER=none
105:ENABLE_OPENDKIM=1
133:ENABLE_CLAMAV=0
144:ENABLE_RSPAMD=0
198:ENABLE_AMAVIS=1
216:ENABLE_FAIL2BAN=0
243:SSL_TYPE=manual (changed: upstream default is empty = no TLS)
388:ENABLE_SPAMASSASSIN=0
TLS: Docker Mailserver offers login for sending only after STARTTLS (see the 587 test below), so it needs a certificate. The lab used SSL_TYPE=manual with a self-signed certificate in docker-data/dms/config/ssl/ (SSL_CERT_PATH/SSL_KEY_PATH); on a real server use SSL_TYPE=letsencrypt or your own certificate, as described in the DMS SSL guide.
First start: create an account within 120 seconds
$ docker compose up -d
$ docker logs mailserver
... INFO start-mailserver.sh: Welcome to docker-mailserver v16.0.1
... INFO start-mailserver.sh: Checking configuration
... INFO start-mailserver.sh: Configuring mail server
... WARN start-mailserver.sh: You need at least one mail account to start Dovecot (120s left for account creation before shutdown)
... WARN start-mailserver.sh: You need at least one mail account to start Dovecot (110s left for account creation before shutdown)
Docker Mailserver will not finish starting without a mailbox, and it stops after 120 seconds. Create the first account right away:
$ docker exec mailserver setup email add admin@example.test 'Adm1n-pass-2026'
$ docker exec mailserver setup email add user1@example.test 'User1-pass-2026'
$ docker exec mailserver setup alias add info@example.test user1@example.test
$ docker exec mailserver setup alias list
* info@example.test user1@example.test
Leave out the password (docker exec -it mailserver setup email add user@example.com) to be prompted for it, so it does not end up in your shell history. Accounts are stored as SHA512-CRYPT hashes in the config directory, which is what you back up:
$ cat docker-data/dms/config/postfix-accounts.cf
admin@example.test|{SHA512-CRYPT}<hash>
user1@example.test|{SHA512-CRYPT}<hash>
$ cat docker-data/dms/config/postfix-virtual.cf
info@example.test user1@example.test
About 30 seconds later all services listened: 25, 143, 465, 587 and 993 on all addresses, plus internal ports for Amavis (10024/10025) and OpenDKIM/OpenDMARC (8891/8893) on localhost. supervisorctl status showed amavis, dovecot, opendkim, opendmarc, postfix and rsyslog running; clamav, fail2ban, rspamd and postgrey stopped, as configured. The container used about 99 MB of RAM in this setup (ClamAV off).
Send mail: port 25 for incoming, 587 for your users

Docker Mailserver uses separate ports for incoming mail and for your users. Port 25 is where other mail servers deliver to you. It accepts mail for your domains and refuses to relay anywhere else:
$ swaks --server mailserver --port 25 --helo client.example.org --from tester@gmail.com --to user1@example.test --header 'Subject: test via port 25'
<- 250 2.0.0 Ok: queued as 1FA17E77
$ swaks --server mailserver --port 25 --helo client.example.org --from tester@gmail.com --to info@example.test --header 'Subject: to the alias'
<- 250 2.0.0 Ok: queued as 727D7E88
$ swaks --server mailserver --port 25 --from someone@outside.test --to victim@gmail.com
<** 554 5.7.1 <victim@gmail.com>: Relay access denied
Port 587 (submission) is for your own users. Authentication is only offered after STARTTLS:
$ swaks --server mailserver --port 587 --auth-user user1@example.test --auth-password '...' ...
*** Host did not advertise authentication
$ swaks --server mailserver --port 587 --tls --from user1@example.test --to admin@example.test --auth-user user1@example.test --auth-password 'User1-pass-2026'
<- 250-STARTTLS
-> STARTTLS
=== TLS started with cipher TLSv1.3:TLS_AES_256_GCM_SHA384:256
<~ 235 2.7.0 Authentication successful
<~ 250 2.0.0 Ok: queued as D44F1E97
$ swaks ... --port 587 --tls --auth-user user1@example.test --auth-password 'wrong'
<~* 535 5.7.8 Error: authentication failed: (reason unavailable)
In a mail client connected to Docker Mailserver, use port 587 with STARTTLS (or 465 with SSL/TLS) and the full email address as user name.
Read mail over IMAPS
$ curl -sk --url 'imaps://mailserver/INBOX' --user 'user1@example.test:User1-pass-2026' -X 'SEARCH ALL'
* SEARCH 1 2
$ curl -sk --url 'imaps://mailserver/INBOX;UID=1' --user 'user1@example.test:User1-pass-2026' | grep -E '^(Subject|From|To):'
To: user1@example.test
From: tester@gmail.com
Subject: test via port 25
$ docker exec mailserver doveadm mailbox status -u user1@example.test messages INBOX
INBOX messages=2
Docker Mailserver delivered both messages, the direct one and the one sent to the alias info@. Mailboxes are Maildir folders under docker-data/dms/mail-data/example.test/<user>/. A wrong password made curl exit with code 67 (login denied). The self-signed certificate is visible to clients (Verify return code: 18 (self-signed certificate)), so mail apps will warn until you use a real certificate.
DKIM keys and the DNS record
$ docker exec mailserver setup config dkim
... INFO open-dkim: Creating DKIM private key '/tmp/docker-mailserver/opendkim/keys/example.test/mail.private'
$ cat docker-data/dms/config/opendkim/keys/example.test/mail.txt
mail._domainkey IN TXT ( "v=DKIM1; h=sha256; k=rsa; "
"p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk5ESAKa+ExBsrxFm..."
"...IZ0xi2liXJwvUH/QIDAQAB" ) ; ----- DKIM key mail for example.test
Docker Mailserver signs outgoing mail once the key exists. Publish that TXT record as mail._domainkey.example.test (joining the quoted parts), then add SPF and DMARC records. Without DKIM, SPF and DMARC, mail from Docker Mailserver is likely to land in spam at the big providers. The record format and checks are in the DKIM, DMARC and SPF guide.
Five Docker Mailserver errors from the test
1. "Permission denied" on the config directory (SELinux). The first run on a Fedora host with SELinux enforcing failed to create any account:
/usr/local/bin/helpers/database/db.sh: line 110: /tmp/docker-mailserver/postfix-accounts.cf: Permission denied
... ERROR listmailuser: '/tmp/docker-mailserver/postfix-accounts.cf' does not exist
On SELinux hosts (Fedora, RHEL, Rocky, Alma) add :Z to each bind mount, for example ./docker-data/dms/config/:/tmp/docker-mailserver/:Z. With that change everything else in this guide worked.
2. "Sender address rejected: Domain not found". A test from a made-up sender domain was refused on port 25: 450 4.1.8 <someone@outside.test>: Sender address rejected: Domain not found. Postfix checks that the sender domain exists in DNS; test with a real domain.
3. "Helo command rejected: need fully-qualified hostname". Next, the client introduced itself with its container ID: 504 5.5.2 <937c1ec95736>: Helo command rejected: need fully-qualified hostname. Real mail servers send their FQDN; in tests pass one (swaks --helo client.example.org).
4. The new password does not work yet. Right after setup email update user1@example.test 'New-pass-2026' an IMAP login with the new password failed (curl exit 67); 20 seconds later it worked. DMS applies account changes through its change detector, so give it a few seconds.
5. Users can send as anyone. With the default SPOOF_PROTECTION= (empty), user1 logged in and sent as admin@example.test, and the server accepted it (250 2.0.0 Ok: queued as 47AB2EAD). The comment in mailserver.env says it plainly: "Any logged in user may create email messages with a forged sender address". Set SPOOF_PROTECTION=1 for any server with more than one user.
Day-to-day commands
$ docker exec mailserver setup email del -y admin@example.test
... INFO delmailuser: Deleting mailbox data: '/var/mail/example.test/admin'
... INFO delmailuser: 'admin@example.test' and associated data (aliases, quotas) deleted
$ docker exec mailserver setup email list
* user1@example.test ( 0 / ~ ) [0%]
[ aliases -> info@example.test ]
$ docker exec mailserver postqueue -p | tail -1
Mail queue is empty
Note that in Docker Mailserver email del removes the mailbox data too. Other useful subcommands from setup help: email restrict, quota, relay (send through a smarthost when your provider blocks port 25) and debug. Logs are in docker-data/dms/mail-logs/mail.log.
If you compare options, the classic all-in-one alternative on this blog is Zimbra on Ubuntu, and for a plain Postfix relay see Postfix with Gmail as relay. The full Docker Mailserver documentation, including which port does what, is at docker-mailserver.github.io.
Comments
Post a Comment