Deploy Your Own Mail Server with mailcow and Docker

Mailcow: dockerized is an open-source email server suite, emphasising on Docker for its deployment, ensuring a modular and containerised approach to manage email services. 

It features a comprehensive mail solution with automatic Let's Encrypt certificate generation, antivirus scanning, IMAP/POP servers, database management, caching, web serving, spam filtering, and more, each run in separate containers for robustness and scalability. 

Central to its functionality is the Mailcow UI, providing a user-friendly interface for managing domains, email addresses, and complex configurations effortlessly. It supports advanced features like DKIM and ARC, spam management, temporary aliases, and two-factor authentication. 

The project also highlights the importance of backing up Docker volumes to safeguard mail data, emphasising encrypted storage for security. Community and commercial support options are available, including documentation and a vibrant community for help. 

For anyone looking to deploy a reliable and manageable mail server, Mailcow: dockerized offers a powerful, flexible solution.

We use Mailcow as our primary mail system, so we've learnt a few things about deploying a successful and performant system. Let's get started!

First of all, you'll need to set up a Linux server or virtual machine. If you deploy a virtual machine, make sure it's HVM or full virtualisation rather than LX, LXC or LXD.

In this example, we're using Debian Bookworm in a bhyve container, running on Clara Compute.

We recommend the following specifications as a minimum;

  • At least 4 CPUs
  • At least 8GB RAM
  • 32GB / EXT4 volume
  • At least 8GB swap volume
  • Large /var/lib/docker XFS volume

Choose minimal installation defaults.

Download procedure;

sudo su
systemctl disable ufw && systemctl stop ufw
apt -y update && apt -y upgrade && apt -y install git
curl -sSL https://get.docker.com/ | CHANNEL=stable sh && systemctl enable --now docker
cd /opt && git clone https://github.com/mailcow/mailcow-dockerized && cd mailcow-dockerized && ./generate_config.sh


Make sure you've got the minimum DNS setup;

;; CNAME Records
autoconfig.$domain.				​1	IN	CNAME	$mailserver.
autodiscover.$domain.				​1	IN	CNAME	$mailserver.

;; MX Records
$domain.						​1	IN	MX	1 $mailserver.

;; SRV Records
_autodiscover._tcp.$domain.		​1	IN	SRV	0 1 443 $mailserver.
_caldavs._tcp.$domain.				​1	IN	SRV	0 1 443 $mailserver.
_carddavs._tcp.$domain.				​1	IN	SRV	0 1 443 $mailserver.
_imaps._tcp.$domain.				​1	IN	SRV	0 1 993 $mailserver.
_imap._tcp.$domain.				​1	IN	SRV	0 1 143 $mailserver.
_pop3s._tcp.$domain.				​1	IN	SRV	0 1 995 $mailserver.
_pop3._tcp.$domain.				​1	IN	SRV	0 1 110 $mailserver.
_sieve._tcp.$domain.				​1	IN	SRV	0 1 4190 $mailserver.
_smtps._tcp.$domain.				​1	IN	SRV	0 1 465 $mailserver.
_submission._tcp.$domain.		​1	IN	SRV	0 1 587 $mailserver.

;; TXT Records
_caldavs._tcp.$domain.				​1	IN	TXT	"path=/SOGo/dav/"
_carddavs._tcp.$domain.				​1	IN	TXT	"path=/SOGo/dav/"
dkim._domainkey.$domain.		​1	IN	TXT	"$key"
_dmarc.$domain.						​1	IN	TXT	"v=DMARC1; p=reject; rua=mailto:mail-dmarc@$dmarcdomain; ruf=mailto:mail-dmarc@$dmarcdomain; adkim=r; aspf=r; pct=100; rf=afrf; ri=86400; sp=reject"
$domain.						​1	IN	TXT	"v=spf1 mx a -all"


Replace;
$domain with your domain name
$mailserver with your mail server's FQDN

Configuration procedure;

  • Enter server details according to the FQDN and PTR records you have configured
  • Use the server's physical location for the time zone
  • Confirm that all the required DNS records exist and are correct


Edit the configuration file (mailcow.conf);

WATCHDOG_NOTIFY_EMAIL=​$alertemail
WATCHDOG_SUBJECT=$mailserver
WATCHDOG_EXTERNAL_CHECKS=y
ACME_CONTACT=$alertemail
SOLR_HEAP=2048
HTTP_BIND=$localip
HTTPS_BIND=$localip


Replace;
$alertemail with the email address for your alerting system
$mailserver with your mail server's FQDN
$localip with your Debain instance's internal IP address

Make sure you've got the minimum firewall setup;

Service				​Protocol		​Port
Postfix SMTP			​TCP				​​25
Postfix SMTPS			​TCP				​465
Postfix Submission	​TCP			​587
Dovecot IMAP			​TCP				​143
Dovecot IMAPS			​TCP				​993
Dovecot POP3			​TCP				​110
Dovecot POP3S			​TCP				​995
Dovecot ManageSieve	​TCP				​4190
HTTP(S)				​TCP				​80/443

Depending on how your network is configured, you'll either need to allow inbound traffic on these ports, set up a 1:1 NAT or configure port forwarding.

Installation procedure;

docker compose pull && docker compose up -d


Your system will start pulling down the images it needs and doing startup tasks. This generally takes a few minutes.


Post-install steps;

Log in to `https://$mailserver` with `admin:moohoo` and change password.


Replace;
$mailserver with your mail server's FQDN


Edit bucket size (data/conf/nginx/site.conf)

server_names_hash_bucket_size 256;


Create a new config file (data/conf/nginx/redirect.conf)

server {
  root /web;
  listen 80 default_server;
  listen [::]:80 default_server;
  include /etc/nginx/conf.d/server_name.active;
  if ( $request_uri ~* "%0A|%0D" ) { return 403; }
  location ^~ /.well-known/acme-challenge/ {
    allow all;
    default_type "text/plain";
  }
  location / {
    return 301 https://$host$uri$is_args$args;
  }
}


Recreate the containers;

docker compose up -d && sleep 600 && docker compose restart nginx-mailcow && echo Done.


Create a new config file (data/conf/rspamd/local.d/dmarc.conf)

reporting {
    enabled = true;
    email = 'noreply-dmarc@$domain';
    domain = '$domain';
    org_name = '$org';
    helo = 'rspamd';
    smtp = 'postfix';
    smtp_port = 25;
    from_name = 'DMARC Report from $org';
    msgid_from = 'rspamd.$mailserver';
    max_entries = 2k;
    keys_expire = 2d;
}


Replace;
$domain with your domain name
$mailserver with your mail server's FQDN


Create a new config file (docker-compose.override.yml)

version: '2.1'

services:
  rspamd-mailcow:
    environment:
      - MASTER=${MASTER:-y}
    labels:
      ofelia.enabled: "true"
      ofelia.job-exec.rspamd_dmarc_reporting.schedule: "@every 24h"
      ofelia.job-exec.rspamd_dmarc_reporting.command: "/bin/bash -c \"[[ $${MASTER} == y ]] && /usr/bin/rspamadm dmarc_report > /var/lib/rspamd/dmarc_reports_last_log 2>&1 || exit 0\""
  ofelia-mailcow:
    depends_on:
      - rspamd-mailcow


Edit config file (data/conf/sogo/sogo.conf)

WOWorkersCount = "40";


Recreate the containers;

docker compose up -d && docker compose restart sogo-mailcow && sleep 600 && echo Done.


Create cron job to empty Junk automatically;

mkdir -p /root/crons && nano /root/crons/clear_junk.sh


#!/bin/bash
cd /opt/mailcow-dockerized
docker-compose exec dovecot-mailcow doveadm expunge -A mailbox 'Junk' SEEN not SINCE 28d
docker-compose exec dovecot-mailcow doveadm expunge -A mailbox 'Junk' savedbefore 84d
docker-compose exec dovecot-mailcow doveadm expunge -A mailbox 'Trash' SEEN not SINCE 28d
docker-compose exec dovecot-mailcow doveadm expunge -A mailbox 'Trash' savedbefore 84d


chmod +x /root/crons/clear_junk.sh
crontab -e


# Clear junk and trash
0 4 * * * /root/crons/clear_junk.sh


From here, you just need to set up your mail domains and general preferences. Once you've set up your primary domain, make sure you take the DKIM key that gets generated and replace the $key record in your DNS with it.


You'll also need to set up DMARC reporting for any additional domains with DNS records too.


A TXT record like;

$secdomain._report._dmarc.$domain


With the content;

v=DMARC1;


Replace;
$domain with your domain name
$secdomain with your additional domain name


Is generally enough.


We also recommend getting yourself set up on https://www.dnswl.org too.


If you're looking for a managed email service, we can offer hosted Mailcow, Exchange and Microsoft 365 depending on your needs. We handle the server setup and maintainance, and can even look after DNS and mailbox administration for you too.


Contact Us if you'd like to know more.

Easily Deploy parsedmarc, geoip and Kibana with Docker