Dockerized email

I’ve been running my personal email server for almost 6 years. The initial setup was on “bare metal”. Afterwards only little maintenance time was needed. Recently I decided to reduce number of servers and moved to another provider. To avoid migrating “bare metal” setup, I dockerized it. It is still not very easy to use compared to existing solutions, but works for my scale.

The dockerized image source is on GitHub and supports:

  • postfix with virtual users based on file setup
  • dovecot for imap, auth and sieve for filtering
  • encrypted mail directories by default for all users with mail-crypt-plugin
  • spamassassin for spam filtering
  • DKIM optionally

Runit to supervise multiple docker processes

When I was dockerizing all email related service, I was not aware of the best docker practices to make a single image with multiple services, but run them in separate containers. At that time I decided to make a single container to supervise multiple processes in a single docker container. runit init system was used. Most of the services was easy to add, but postfix required a special attention due to isolation. The final list of services:

# cat /service/postfix/run 
/usr/lib/postfix/ postfix
exec /usr/lib/postfix/sbin/master

# cat /service/cron/run
exec cron -f

# cat /service/dovecot/run
exec dovecot -F

# cat /service/opendkim/run
exec opendkim -x /etc/opendkim.conf -f

# cat /service/rsyslog/run
exec rsyslogd -n

# cat /service/spamassassin/run
exec spamd --max-children 5

In the future I am planning to split these service into multiple containers according to the best docker practices.

Virtual users, domains and aliases

A few files have to be mounted for proper setup:

  • virtual_domains which contains list of domains for virtual transport
  • virtual_boxes describes accounts for the virtual transport
  • virtual_aliases adds additional aliases

Encrypted mailboxes

All mailboxes are encrypted by default with dovecot mail-crypt plugin. /etc/dovecot/users is used to configure user passwords. To generate a new password you can use

doveadm pw -s SHA512-CRYPT -p "<PASSWORD>"

NOTE: If user’s password is lost, mailbox cannot be decrypted anymore

To change user’s password it is not enough to change it in /etc/dovecot/users. Prevoius email won’t be possible to decrypt anymore. To set new password for the private key run:

doveadm mailbox cryptokey password -u "$user" -n "$password_new1" -o "$password_old"


To support renewable certificates it is better to mount the whole folder and provide CERT_LOCATION and CERT_KEY_LOCATION. If you mount files directly, inode might change during certificate renewal and postfix/dovecot won’t be able to use new cert until container is restarted.


DKIM setup is not trivial and requires manual setup of private and public keys for all your virtual domains. Later I plan to simplify it.

With docker

To run this container with docker:

docker run -d \
    -e \
    -e DKIM_ENABLED=true \
    -e CERT_LOCATION=/etc/letsencrypt/live/ \
    -e CERT_KEY_LOCATION=/etc/letsencrypt/live/ \
    -p 25:25 \
    -p 143:143 \
    -p 587:587 \
    -v /etc/letsencrypt/live/ \
    -v /etc/letsencrypt/live/ \
    -v /path/to/dkim:/etc/opendkim \
    -v /path/to/mail:/mail \
    -v /path/to/users:/etc/dovecot/users \
    -v /path/to/virtual_domains:/etc/postfix/virtual_domains \
    -v /path/to/virtual_accounts:/etc/postfix/virtual_boxes \
    -v /path/to/virtual_aliases:/etc/postfix/virtual_aliases \

With docker-compose

To run this container with docker-compose:

    container_name: myemail
    hostname: myemail
    image: postfix-dovecot
      - /etc/letsencrypt/live/
      - /etc/letsencrypt/archive/
      - /path/to/dkim:/etc/opendkim
      - /path/to/mail:/mail
      - /path/to/users:/etc/dovecot/users
      - /path/to/virtual_domains:/etc/postfix/virtual_domains
      - /path/to/virtual_accounts:/etc/postfix/virtual_boxes
      - /path/to/virtual_aliases:/etc/postfix/virtual_aliases
      - DKIM_ENABLED=true
      - CERT_LOCATION=/etc/letsencrypt/live/
      - CERT_KEY_LOCATION=/etc/letsencrypt/live/
      - 25:25
      - 143:143
      - 587:587
    restart: unless-stopped

comments powered by Disqus