A set of docker images and instructions that can be used to set up a simple mail server for a single user.
Features:
- 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
Usage
Initialize your repo and set up some configs
-
Add this repo as a submodule
git submoudle add https://github.com/vikstrous/mailserver git submodule update --init -
Link the docker compose file
ln -s ./mailserver/docker-compose.yml -
Create the directories used for secrets:
mkdir -p ./cert ./dkim -
Create a file called
./envfilling out the following data:mail_for_fqdn= mailserver_fqdn= user=- The mail server should be at the
mail.subdomain to make autodiscovery work easily in mail clients. In other words, ifmail_for_fqdnis example.commailserver_fqdnis mail.example.com - 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.
- The mail server should be at the
-
Generate a password hash for auth
- Use
python3 -c 'import crypt; print(crypt.crypt("password", crypt.mksalt(crypt.METHOD_SHA512)))', replacingpasswordwith your password - Put the result in
./envaspass=$6$...
- Use
-
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
-
Get a server - aws, vultr, digital ocean, linode, whatever. You need 512MB of RAM.
-
Get a domain and add an A record for your mail subdomain.
-
Install docker
curl https://get.docker.com | sudo sh(known to work with docker 1.12) -
For convenience
sudo usermod -aG docker your-userif not using the root user -
Get docker compose:
curl -L "https://github.com/docker/compose/releases/download/1.9.0/docker-compose-$(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.txtgenerated 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
- Set up a TXT record with the key
- SPF
- Create a TXT record with the key
@and value"v=spf1 mx -all"
- Create a TXT record with the key
- DMARC
- Create a TXT record with the key
_dmarcand valuev=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.
- Create a TXT record with the key
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 quay.io/letsencrypt/letsencrypt:latest -c "certbot certonly --non-interactive --standalone --agree-tos --email a@example.com -d $MAILSERVER_FQDN && tar -c /etc/letsencrypt/archive/$MAILSERVER_FQDN > /out/certs.tar" cd outtar -xvf certs.tar- Your certs are in
etc/letsencrypt/archive/$MAILSERVER_FQDN/fullchain1.pemandetc/letsencrypt/archive/$MAILSERVER_FQDN/privkey1.pem. Copy them to your project as./cert/cert.pemand./cert/key.pemrespectively
-
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
Run
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.
Test
- https://mail-tester.com/
- https://mxtoolbox.com/diagnostic.aspx
- https://checktls.com/perl/TestSender.pl
- https://checktls.com/perl/TestReceiver.pl
Extra configs
- To configure sieve copy
sieve.examplefrom the root of this repo into./sieve/sievein your repo then modify it to fit your needs
Upgrade
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
- Note your current DNS TTL value -> X seconds
- Set your DNS's TTL to a low value like 10 seconds
- Wait X seconds for any downstream DNS servers to update their cache
- Add a DNS record for your new domain to point to your mail server (in addition to the old one)
- Wait for DNS to start working
- Get new TLS certs using the let's encrypt method I described above
- Set up any additional DNS records for DKIM, etc. by following the original instructions for setting up a mail server.
- Replace and commit the new certs to your repo
- Check out the new version on your server
- Run
docker-compose up --force-recreate -dto update the server - 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.