Docker Mailserver Upgrade to v16: 4 Steps and 3 Mistakes

A docker mailserver upgrade from 15.x to 16.x is not just a new image tag. Version 16.0.0 moves the image from Debian 12 to Debian 13 and from Dovecot 2.3 to Dovecot 2.4. Your mail and accounts survive the switch, but an old dovecot.cf with 2.3 setting names stops Dovecot completely, SA_SPAM_SUBJECT is rejected, and DHE ciphers disappear from ports 587 and 993. The container still reports "up and running". Below is the full upgrade on a test server, what broke and the exact fixes.

Lab: docker-mailserver 15.1.0 upgraded to 16.0.1 on the same volumes, rootless podman 5.8 (the docker commands are identical), a Debian 13 client container with swaks, curl and openssl, October 2026. Every output below comes from that lab. Sources: the docker-mailserver v16.0.0 release, its CHANGELOG and the Dovecot 2.3 to 2.4 upgrade guide.

Docker mailserver upgrade to v16, in short:

  1. Back up docker-data/dms/ and stop the container.
  2. Rename 2.3 settings in config/dovecot.cf (for example disable_plaintext_auth becomes auth_allow_cleartext).
  3. Rename SA_SPAM_SUBJECT to SPAM_SUBJECT in mailserver.env.
  4. Change the image tag to 16.0.1, start it and check doveconf -n and an IMAP login, not only the "up and running" line.

What changes in docker-mailserver 16

Most of the docker mailserver upgrade work comes from the base image change brings new major versions of the main services. Before and after, from the lab:

# docker exec mailserver bash -c 'cat /etc/os-release | grep PRETTY; dovecot --version; postconf -h mail_version'
PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"
2.3.19.1 (9b53102964)
3.7.11
# docker exec mailserver bash -c 'cat /etc/os-release | grep PRETTY; dovecot --version; postconf -h mail_version' 2>&1
PRETTY_NAME="Debian GNU/Linux 13 (trixie)"
2.4.1-4 (7d8c0e5759)
3.10.13

The changes that can break an existing install during a docker mailserver upgrade, according to the changelog:

Change in v16Who is affected
Dovecot 2.3 to 2.4: "Custom dovecot.cf using 2.3 syntax will not load"Anyone with config/dovecot.cf or mounted Dovecot files
SA_SPAM_SUBJECT no longer accepted, use SPAM_SUBJECTSpamAssassin users with an old env file
Bundled DH parameters and dhparams.pem override removed, no DHE on 587/465/143/993/110/995Very old mail clients that only speak DHE
LDAP mailStorageDirectory is a path only (no maildir: prefix)ACCOUNT_PROVISIONER=LDAP
Pyzor and Razor removedSpamAssassin setups that relied on them
All DMS logs go to STDERRLog filters that read only stdout

The lab covers the first three. The LDAP change was not tested.

The test server on 15.1.0

To make the docker mailserver upgrade realistic, the v15 setup used options that many older installs still carry: SpamAssassin with SA_SPAM_SUBJECT, a dovecot.cf with two common settings and a custom dhparams.pem:

# cat mailserver.env; cat dms/config/dovecot.cf; ls dms/config
SSL_TYPE=manual
SSL_CERT_PATH=/tmp/docker-mailserver/ssl/cert.pem
SSL_KEY_PATH=/tmp/docker-mailserver/ssl/key.pem
ENABLE_SPAMASSASSIN=1
ENABLE_AMAVIS=1
SA_SPAM_SUBJECT=***SPAM***
PERMIT_DOCKER=none
mail_max_userip_connections = 50
disable_plaintext_auth = yes
dhparams.pem
dovecot.cf
ssl

Two accounts, one alias and three messages for user1. Message 2 was flagged so we can see later whether flags survive the docker mailserver upgrade:

# docker exec mailclient curl -sk --url 'imaps://mailserver/INBOX' --user 'user1@example.test:User1-pass-2026' -X 'SEARCH ALL'
* SEARCH 1 2 3

# docker exec mailclient curl -sk --url 'imaps://mailserver/INBOX' --user 'user1@example.test:User1-pass-2026' -X 'STORE 2 +FLAGS (\Seen \Flagged)'; docker exec mailclient curl -sk --url 'imaps://mailserver/INBOX' --user 'user1@example.test:User1-pass-2026' -X 'SEARCH FLAGGED'
* 2 FETCH (FLAGS (\Flagged \Seen))
* SEARCH 2

On 15.1.0 a client could still negotiate a DHE cipher on IMAPS and submission:

# docker exec mailclient bash -c 'echo | openssl s_client -connect mailserver:993 -tls1_2 -cipher DHE-RSA-AES256-GCM-SHA384 2>&1 | grep -E "Cipher is|Server Temp Key|alert|handshake failure" | head -3'
New, TLSv1.2, Cipher is DHE-RSA-AES256-GCM-SHA384

Step 1: back up before the docker mailserver upgrade

Every docker mailserver upgrade should start with a backup. Stop the container cleanly so Postfix and Dovecot flush their queues and indexes, then copy the whole data folder. With rootless podman many files belong to sub-UIDs: in the lab a plain du on mail-state printed "Permission denied" for the Postfix spool, so make the copy as root or with podman unshare.

  • Stop the stack with docker compose down (the lab used docker stop -t 60 mailserver).
  • Archive the whole docker-data/dms folder as root, for example with tar -czf.
  • Write down the image tag from your compose.yaml.

Pin the old tag (for example 15.1.0) instead of latest, so a rollback is one edit.

Step 2: what happens if you only change the image

This is the trap of a docker mailserver upgrade. With the old config and image: ghcr.io/docker-mailserver/docker-mailserver:16.0.1, the container started and logged that it is up:

# docker logs mailserver 2>&1 | grep -E 'Welcome|SA_SPAM_SUBJECT|Fatal|up and running' | head -8
2026-10-25 13:29:56+00:00 ERROR start-mailserver.sh: 'SA_SPAM_SUBJECT' has been renamed to 'SPAM_SUBJECT' since DMS v16
2026-10-25 13:29:56+00:00 INFO  start-mailserver.sh: Welcome to docker-mailserver v16.0.1
doveconf: Fatal: Error in configuration file /etc/dovecot/local.conf line 2: disable_plaintext_auth: Unknown setting: disable_plaintext_auth
2026-10-25 13:29:58+00:00 INFO  start-mailserver.sh: mail.example.test is up and running
docker mailserver upgrade to 16.0.1: Dovecot fails on disable_plaintext_auth while the container says up and running
15.1.0 config on 16.0.1: the container is "up and running", Dovecot is not.

Dovecot did not start at all. Supervisor kept restarting it, and IMAP was closed:

# docker logs mailserver 2>&1 | grep -c 'exited: dovecot (exit status 89'
12

# docker exec mailclient curl -sk --url 'imaps://mailserver/INBOX' --user 'user1@example.test:User1-pass-2026' -X 'SEARCH ALL'; echo curl rc=$?
curl rc=7

curl exit code 7 means "failed to connect": nothing listened on 993. At the same moment supervisorctl status showed dovecot RUNNING, because it caught one of the restart attempts. Do not trust the "up and running" line or the process list after a docker mailserver upgrade; test a real login.

/etc/dovecot/local.conf in the error is your docker-data/dms/config/dovecot.cf: the DMS Dovecot docs say the file "is copied internally to /etc/dovecot/local.conf". Note also that the SA_SPAM_SUBJECT message is logged as ERROR, but the start continues.

Step 3: convert dovecot.cf to Dovecot 2.4 names

The fix for this docker mailserver upgrade error is in your own config. Check every line of your dovecot.cf against the "Converted Settings" table of the Dovecot upgrade guide. A few entries that often appear in DMS overrides:

Dovecot 2.3Dovecot 2.4
disable_plaintext_authreplaced by auth_allow_cleartext
ssl_cert / ssl_keyssl_server_cert_file / ssl_server_key_file
ssl_dhssl_server_dh_file
quota_gracequota_storage_grace
plugin { ... } block"All plugin settings are now global settings"
service { service_count }service_restart_request_count

Watch the inverted meaning: disable_plaintext_auth = yes becomes auth_allow_cleartext = no. Settings that still exist in 2.4, like mail_max_userip_connections, stay as they are. The fixed file and env:

# cat dms/config/dovecot.cf; grep SPAM_SUBJECT mailserver.env
mail_max_userip_connections = 50
auth_allow_cleartext = no
SPAM_SUBJECT=***SPAM***

Before the real switch, test a new dovecot.cf with doveconf -n inside the 16.0.1 image. Any unknown name prints the same doveconf: Fatal line as above. After the fix it parsed cleanly:

# docker exec mailserver doveconf -n 2>&1 | grep -E 'mail_max_userip|auth_allow_cleartext|ssl_server_dh_file|ssl_server_cert_file'
mail_max_userip_connections = 50

auth_allow_cleartext does not show up because doveconf -n only prints values that differ from the default, and no is already the default.

Step 4: start 16.0.1 and verify mail, flags and sending

With the fixed config the docker mailserver upgrade went through. Accounts, aliases, messages and flags were all there:

# docker exec mailserver setup email list
* user1@example.test ( 4.0K / ~ ) [0%]
    [ aliases -> info@example.test ]

* admin@example.test ( 0 / ~ ) [0%]

# docker exec mailclient curl -sk --url 'imaps://mailserver/INBOX' --user 'user1@example.test:User1-pass-2026' -X 'SEARCH ALL'
* SEARCH 1 2 3

# docker exec mailclient curl -sk --url 'imaps://mailserver/INBOX' --user 'user1@example.test:User1-pass-2026' -X 'SEARCH FLAGGED'
* SEARCH 2

# docker exec mailclient curl -sk --url 'imaps://mailserver/INBOX;UID=2' --user 'user1@example.test:User1-pass-2026' | grep -E '^Subject:'
Subject: before upgrade 2

Receiving on port 25 (through the alias) and sending on 587 with STARTTLS and authentication both worked:

# docker exec mailclient swaks --server mailserver --port 25 --helo client.example.org --from tester@gmail.com --to info@example.test --header 'Subject: after upgrade' 2>&1 | grep -E '^<\*\*|250 2.0.0 Ok: queued'
<-  250 2.0.0 Ok: queued as E51E41169

# docker exec mailclient swaks --server mailserver --port 587 --tls --from user1@example.test --to admin@example.test --auth-user user1@example.test --auth-password 'User1-pass-2026' 2>&1 | grep -E '235 2.7.0|250 2.0.0 Ok|^<\*\*' | head -3
<~  235 2.7.0 Authentication successful
<~  250 2.0.0 Ok: queued as 2D83F117B

# docker exec mailserver doveadm mailbox status -u user1@example.test messages INBOX; docker exec mailserver doveadm mailbox status -u admin@example.test messages INBOX
INBOX messages=4
INBOX messages=1

The spam subject still arrives in the generated settings under its new name: /etc/dms-settings contained SPAM_SUBJECT='***SPAM***' on both versions.

DHE ciphers are gone on 587 and 993

One docker mailserver upgrade side effect you only notice with old clients: v16 removed the bundled DH parameters (pull request #4562), and the dhparams.pem in the config folder was ignored without a warning. The same DHE handshake that worked on 15.1.0 now fails:

# docker exec mailclient bash -c 'echo | openssl s_client -connect mailserver:993 -tls1_2 -cipher DHE-RSA-AES256-GCM-SHA384 2>&1 | grep -E "Cipher is|Server Temp Key|alert|handshake failure" | head -3'
80D7EBD0A37F0000:error:0A000410:SSL routines:ssl3_read_bytes:ssl/tls alert handshake failure:../ssl/record/rec_layer_s3.c:918:SSL alert number 40
New, (NONE), Cipher is (NONE)

# docker exec mailclient bash -c 'echo | openssl s_client -connect mailserver:993 -tls1_2 2>&1 | grep -E "Cipher is"'
New, TLSv1.2, Cipher is ECDHE-RSA-AES128-GCM-SHA256

Port 587 failed the same way; port 25 still negotiated DHE-RSA-AES256-GCM-SHA384, as the changelog says. On the server side the failed client shows up in mail.log as SSL_accept() failed: error:0A0000C1:SSL routines::no shared cipher. Normal clients use ECDHE and are not affected. If an old device needs DHE, follow "Using custom DH parameters" in the DMS v16 SSL docs.

Healthcheck and other details

  • dms-healthcheck is new in v16 and returned rc=0 on the working server. Under podman, docker inspect -f '{{json .Config.Healthcheck}}' printed null: the changelog notes that podman and Kubernetes must configure the healthcheck explicitly. Setup details are in Docker Mailserver healthcheck in v16.
  • APT sources inside the image are now in DEB822 format: /etc/apt/sources.list.d/ lists debian.sources and upstream-rspamd.sources. Adjust any user-patches.sh that writes .list files.
  • which pyzor razor-check found nothing (exit 1): both tools are gone.

Rollback to 15.1.0

A docker mailserver upgrade should always have a way back. To test the rollback, the lab put the 2.3 dovecot.cf back and started 15.1.0 on the volumes that 16.0.1 had used. All four messages, including the one delivered by v16, were readable:

# docker exec mailclient curl -sk --url 'imaps://mailserver/INBOX' --user 'user1@example.test:User1-pass-2026' -X 'SEARCH ALL'
* SEARCH 1 2 3 4

So a short rollback worked in this test with Maildir storage. It is still not a replacement for the backup from step 1: restore that if v16 ran for a while and you are not sure.

Docker mailserver upgrade checklist

  1. Before the docker mailserver upgrade, read the v16 changelog for your features (LDAP, quotas, SpamAssassin, custom TLS).
  2. Back up docker-data/dms/, write down the current image tag.
  3. Convert dovecot.cf and any mounted Dovecot files to 2.4 names; run doveconf -n in the new image.
  4. Rename SA_SPAM_SUBJECT to SPAM_SUBJECT.
  5. Switch to 16.0.1, then grep the logs for Fatal and ERROR.
  6. Test IMAP login, a mail on port 25 and a send on 587 before you call the docker mailserver upgrade done.

New to DMS? Start with the Docker Mailserver setup guide, which installs 16.0.1 directly.

Comments

Popular posts from this blog

How to Play Minecraft Bedrock Edition on Linux: A Comprehensive Guide for Tech Professionals

The Ultimate Guide: How to Set Up DXVK in Wine on Linux for Enhanced Gaming Performance

Linux desktop frozen? My 5 go-to tricks to try - before forcing a hard reboot