An easy to deploy dockerized mail server for a single user
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Type Name Latest commit message Commit time
Failed to load latest commit information.


A set of docker images and instructions that can be used to set up a simple mail server for a single user.


  • TLS + instructions for how to set up with let's encrypt
  • SMTP and IMAP
  • Simple config - just specify your domains, username, password. The rest is just setting up DNS
  • Catchall domain
  • Spam detection
  • Sieve filter support
  • DKIM signing
  • DKIM, SPF, DMARC set up instructions
  • Instructions for how to test the result

Expected set up time: 1 hour


Initialize your repo and set up some configs

  1. Add this repo as a submodule

    git submoudle add
    git submodule update --init
  2. Link the docker compose file ln -s ./mailserver/docker-compose.yml

  3. Create the directories used for secrets: mkdir -p ./cert ./dkim

  4. Create a file called ./env filling out the following data:

    • The mail server should be at the mail. subdomain to make autodiscovery work easily in mail clients. In other words, if mail_for_fqdn is mailserver_fqdn is
    • Note that this user can be anything and doesn't need to exist on the host. It will be the username used to log into your mail server.
  5. Generate a password hash for auth

    • Use python3 -c 'import crypt; print(crypt.crypt("password", crypt.mksalt(crypt.METHOD_SHA512)))', replacing password with your password
    • Put the result in ./env as pass=$6$...
  6. Generate a DKIM key

    • opendkim-genkey --hash-algorithms sha256 --bits 2048 --domain $MAILSERVER_FQDN --directory ./dkim --selector default --restrict
    • The result will be an RSA key in ./dkim/default.private
    • Don't use keys larger than 4096 for now because verifiers may not support them, which would defeat the whole point of DKIM
    • Note the contents of dkim/default.txt - you will use this for your DKIM DNS records

Create your server and install the basics

  1. Get a server - aws, vultr, digital ocean, linode, whatever. You need 512MB of RAM.

  2. Get a domain and add an A record for your mail subdomain.

  3. Install docker curl | sudo sh (known to work with docker 1.12)

  4. For convenience sudo usermod -aG docker your-user if not using the root user

  5. Get docker compose:

    curl -L "$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
    chmod +x /usr/local/bin/docker-compose

Set up DNS

  • Create an A record for your subdomain
  • Create an MX record with value $MAILSERVER_FQDN and priority 1
  • DKIM
    • Set up a TXT record with the key default._domainkey
    • Use the contents of ./dkim/default.txt generated earlier for the value of the TXT record
    • This should already be done for you by opendkim-genkey, but if not, split up the value using multiple quoted strings separated by spaces such that none of the strings is longer than 256 characters
  • SPF
    • Create a TXT record with the key @ and value "v=spf1 mx -all"
    • Create a TXT record with the key _dmarc and value v=DMARC1; p=reject; rua=mailto:dmarc@$MAIL_FOR_FQDN; ruf=mailto:dmarc-violation@$MAIL_FOR_FQDN; sp=reject; fo=1; aspf=s; adkim=s; pct=100
    • Read up on dmarc and customize the record to your liking. The above example is the strictest possible mode.

TLS cert

Wait for the DNS A record to propagate. Try resolving your hostname from your server a few times until it works, then choose one of the following options to get a TLS cert:

  • Using Let's Encrypt

    • On your server:
    • run this on your server docker run -it --rm -p 443:443 -p 80:80 --name certbot -v $(pwd)/out:/out --entrypoint sh -c "certbot certonly --non-interactive --standalone --agree-tos --email -d $MAILSERVER_FQDN && tar -c /etc/letsencrypt/archive/$MAILSERVER_FQDN > /out/certs.tar"
    • cd out
    • tar -xvf certs.tar
    • Your certs are in etc/letsencrypt/archive/$MAILSERVER_FQDN/fullchain1.pem and etc/letsencrypt/archive/$MAILSERVER_FQDN/privkey1.pem. Copy them to your project as ./cert/cert.pem and ./cert/key.pem respectively
  • Or with a real CA

    • In your project directory

    • Generate the key and csr

      openssl genrsa -out ./cert/key.pem 4096
      openssl req -new -key ./cert/key.pem -out ./cert/csr.pem
    • Get the cert issued and put it in ./cert/cert.pem

  • Or with a self signed cert

    • In your project directory
    • openssl req -x509 -newkey rsa:4096 -keyout ./cert/key.pem -out ./cert/cert.pem -days 365


Now that everything is ready, commit all your secrets to git, push them, then clone the repo onto your server and run docker-compose up -d. Sorry about the lame secret management. Encrypting the secrets is left as an exercise to the reader.


Extra configs

  • To configure sieve copy sieve.example from the root of this repo into ./sieve/sieve in your repo then modify it to fit your needs


In your repo:

cd mailserver
git pull
cd ..
git add mailserver
git commit -m 'update mailserver'
git push

On the server:

docker-compose up --build -d

Changing domains

There is no easy way to do this without down time, so here's one way you can do it while having minimal down time

  1. Note your current DNS TTL value -> X seconds
  2. Set your DNS's TTL to a low value like 10 seconds
  3. Wait X seconds for any downstream DNS servers to update their cache
  4. Add a DNS record for your new domain to point to your mail server (in addition to the old one)
  5. Wait for DNS to start working
  6. Get new TLS certs using the let's encrypt method I described above
  7. Set up any additional DNS records for DKIM, etc. by following the original instructions for setting up a mail server.
  8. Replace and commit the new certs to your repo
  9. Check out the new version on your server
  10. Run docker-compose up --force-recreate -d to update the server
  11. After making sure everything is working, increase your DNS's TTL again and remove any DNS entries you are not using any more

The down time should happen only between step 8 and the end of step 9.