A recent HN discussion got me thinking - why don’t I try and run my own email infrastructure? Some of the main reasons I hadn’t previously tried might come naturally:
- Email servers are a pain to set up
- They’re open to attack
- Hosted email is fairly inexpensive and easy to set up
I recently expanded to having three different domains for email - one for general web logins & CVs, one I give to just family & friends, and the last for my company Jammed. To make things worse, each of these domains is hosted by different providers, so consolidating them all to use one email server would be a fairly good reason to bother.
Well, given I had a Christmas Eve to myself, I decided to try… and it was actually quite a bit easier than I thought it would be.
I’m a bit of a privacy geek, but it wasn’t the driving force behind me owning the infra - but still the thought of all of my business email touching a Google server was always a bit of a sting.
From the off, I had some reasonable requirements:
- It had to use IMAP, SSL/TLS
- I had to be able to use mulltiple email accounts with the same server
- It kinda just had to look after itself once created
- Be reasonably cheap, but not any cheaper than my current three hosted email subsciptions
At first I found a few github projects that were stale, but eventually came across the excellent Docker Mailserver which is a simple Docker Compose container setup that runs a fully fledged mail server.
The blog article is the setup to make Docker Mailserver act like a Gmail server - if you don’t really care about Archive/Junk/All Mail working, you can just use the default setup and be reasonably happy. I’m sure you could go further with this setup, with Kubernetes, but I’m not going to bother as it’s overkill for a single email server.
For this you need
- A Digital Ocean (or similar) account
- A domain name to host it on
- A DNS setup to point the domain to the server (I use Cloudflare)
I spun up a small Digital Ocean droplet with using the ‘Docker’ marketplace image. Docker Mailserver needs at least 2GB of RAM to run - this can be less if you disable virus scanning (which loads the full signature database in memory at boot).
I set mine up in the London region, but anywhere that’s close to you physically is fine.
This is the most important part - you attach a block storage volume to the droplet, so you have the option later of scaling up the storage, independently of the droplet size. The volume is attached to the droplet at boot, and is then mounted on the host machine.
I added a 50GB volume to mine, because it’s easy to scale later.
This should be pretty self-explanatory - email is pretty important data, so it’s important to keep it backed up.
Login to your droplet, and run the following commands:
chmod a+x ./setup.sh
setup.sh is the main script that configures the mail server.
Head to ‘Volumes’ in the Digital Ocean dashboard, and attach the volume to your droplet (unless it already has done so). Next, find the ‘configure your volume’ section, and follow the instructions to mount the volume.
After this point, the droplet will always mount the volume at boot, and the volume will be mounted on the host machine for us to use as the data store for email, config files, etc.
First, at the top of the file, add your full domain name in as the
Now we have a persistent block volume, we can change the mount points in the
docker-compose.yml file to point to the volume.
Mine looks like this:
mnt/volume_lon1_01 is my Digital Ocean block volume, and setting these will mount the Docker volumes on the host machine in the right places.
Follow the instructions to setup Certbot to automatically provision and renew the certificate for the domain you want to use. For Cloudflare, you can use their DNS provisioning plugin using an API key that verifies the domain you want to use without needing to set up a nginx server to serve the challenge.
Make sure you provision a certificate for the subdomain you are using, and it needs to exactly match the host you use for the mailserver. For example, if you are using
mail.example.com as the host, you need to provision a certificate for
mail.example.com and not
example.com or you’ll get IMAP TLS auth errors when you try to connect to the server.
You run certbot on the droplet itself, not in Docker. Once the certificate is provisioned, you then just add the /etc/letsencrypt volume into the
docker-compose.yml file so that the host machine certificates are available to the containers.
docker-mailserver only provides ‘Inbox’, ‘Sent’, ‘Drafts’ folders for IMAP. These are termed ‘Special folders’, and most clients now support ones like ‘Archive’ and ‘Junk’ as well. I really like this feature, and I’m not sure why it’s not in the default, but hey ho! I’ll add it:
To make changes here, we need to configure
dovecot to add these new folders to the IMAP server.
First, grab the
dovecot.conf file from the master branch of the
Now open it and edit it to uncomment the lines about ‘Archive’ & ‘Junk’. I’ll leave this part as your choice. Once you’ve made your changes, modify the
docker-compose.yml file to mount the new
.conf file for
A few things we need to do here:
- Make sure ClamAV is enabled
- Make sure fail2ban is enabled
- Make sure letsecrypt is enable
We can’t start the docker compose yet, as we don’t yet have an initial email account. Instead, spin up a copy of
docker-mailserver with the following command:
docker run --rm -v "/mnt/volume_lon1_01/config/:/tmp/docker-mailserver/" docker.io/mailserver/docker-mailserver setup email add <user@domain>
/mnt/volume_lon1_01/config for your block volume mount point. You’ll be prompted to create a password for the email account.
One mistake I made initially was to start the containers in the background. This is because I was using the
docker-compose up -d mailserver command to start the containers. This is the recommended way to start containers once everything is ready, but to make sure everything is working, use
docker-compose up command to start the containers in the foreground.
Any letencrypt errors may be due to DNS not propogating, or the /etc/letsencrypt/ directory not being mounted correctly.
DKIM is the way to sign emails with your domain’s public key. It’s really important this step is done, as it is one big way to prevent spam from being sent to your email account and a huge signal that this mail server is legit.
./setup.sh config dkim
Add these as a TXT DNS record to the domain you want to use.
Create a new mailbox locally to your machine, and use the IMAP server, email, password you just created to connect to the server. You should see a list of folders, and you should be able to send an email to the mailbox. I found the OSX’s Mail has a Connection Doctor that will tell you if you’re connected to the server, and if not, what error actually occurred. Test send, receive, and delete emails.
A big one for me was to add a second email account. I’m using a different domain, so I need to create a new mailbox for that domain. I’ll do this by running the following command:
DMARC is the method to allow spam and abuse reports to be sent and handled by the email server. I won’t go into it here, but Docker Webserver handles this for you transparently - you just need to add the DNS TXT record to your domain & the email server will do the rest.
It was easier than I thought to create a mail server that works as well as Gmail’s, and now really happy that I’m one of those weirdos that hosts their own mail server.
Andy Callaghan makes Jammed - booking software for music rehearsal studios and recording studios