Setting up mailcow on mail server on an EC2 instance
Recently I've been exploring how we can create our own mail server with our own custom domains. In my research I've come across a tool named mailcow. So let's dive in to learn about mailcow, and how we can set it up in an AWS EC2 instance. So what is mailcow? From the official documentation, it is given that.... mailcow: dockerized is an open source groupware/email suite based on docker. mailcow basically generates a docker-compose file for helping setup a mail server. It already comes with preconfigured components such as acme - For creating Let's encrypt Certificates. ClamAV - For scanning Anti-virus Devcot - For IMAP/POP servers MariaDB - For storing user information SOGo - Integrated Webmailer Memcached - Cache for the webmailer SOGo Nginx - Web server for components of the stack Olefy - Analysis of Office documents for viruses, macros, etc. PHP - Programming language of most web-based mailcow applications Postfix - MTA (Mail Transfer Agent) for e-mail traffic on the Internet Redis - Storage for spam information, DKIM key, etc. Rspamd - Spam filter with automatic learning of spam mails Unbound - Integrated DNS server for verifying DNSSEC etc. etc. etc. How would we install mailcow then? Installing & setting up a mailcow server is very easy, you would just need to basically run a docker-compose file for installation. Then everything can be done from the UI. But, there are some prerequisites we need to follow.... Setting up an EC2 instance. Our mail goal here would be to create an EC2 instance with sufficient RAM & necessary open ports on the server. Note: For best experience it is recommended to use atleast 4GB RAM, that's why we are using t3.medium here. For the creation of the instance I'm using terraform. You can find the full source code from here. First of all for our server we need a few necessary ports to be opened. We can create a security group for this requirement like this... resource "aws_security_group" "mailcow-security-group" { name = "mailcow-sg" description = "Allow TLS inbound traffic" ingress { description = "SSH" from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { description = "SSH" from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { description = "SSH" from_port = 443 to_port = 443 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { description = "SSH" from_port = 25 to_port = 25 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { description = "SSH" from_port = 465 to_port = 465 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { description = "SSH" from_port = 587 to_port = 587 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { description = "SSH" from_port = 143 to_port = 143 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { description = "SSH" from_port = 993 to_port = 993 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { description = "SSH" from_port = 110 to_port = 110 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { description = "SSH" from_port = 995 to_port = 995 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { description = "SSH" from_port = 4190 to_port = 4190 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } tags = { Name = "mailcow-sg" } } Now, we can create a EC2 instance with the Ubuntu image with the above security group, like this... resource "aws_instance" "mailcow_server" { ami = "ami-0e35ddab05955cf57" instance_type = "t3.medium" key_name = aws_key_pair.deployer.key_name vpc_security_group_ids = [aws_security_group.mailcow-security-group.id] user_data = file("${path.module}/install-script.sh") tags = { Name = "mailcow-vm" } } During the creation of the EC2 instance we are including an install script for installing Docker & Docker compose. Here is the script.... #!/bin/bash echo "Starting Docker Install" >> "/home/ubuntu/install-logs" # Add Docker's official GPG key: sudo apt-get update sudo apt-get install ca-certificates curl sudo install -m 0755 -d /etc/apt/keyrings sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc sudo chmod a+r /etc/apt/keyrings/docker.asc # Add the repository to Apt sources: echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ $(. /etc/os-release && echo "${UBUNTU_CODENAME:

Recently I've been exploring how we can create our own mail server with our own custom domains.
In my research I've come across a tool named mailcow
.
So let's dive in to learn about mailcow
, and how we can set it up in an AWS EC2 instance.
So what is mailcow
?
From the official documentation, it is given that....
mailcow: dockerized is an open source groupware/email suite based on docker.
mailcow basically generates a docker-compose file for helping setup a mail server. It already comes with preconfigured components such as
- acme - For creating Let's encrypt Certificates.
- ClamAV - For scanning Anti-virus
- Devcot - For IMAP/POP servers
- MariaDB - For storing user information
- SOGo - Integrated Webmailer
- Memcached - Cache for the webmailer SOGo
- Nginx - Web server for components of the stack
- Olefy - Analysis of Office documents for viruses, macros, etc.
- PHP - Programming language of most web-based mailcow applications
- Postfix - MTA (Mail Transfer Agent) for e-mail traffic on the Internet
- Redis - Storage for spam information, DKIM key, etc.
- Rspamd - Spam filter with automatic learning of spam mails
- Unbound - Integrated DNS server for verifying DNSSEC etc.
- etc. etc.
How would we install mailcow then?
Installing & setting up a mailcow server is very easy, you would just need to basically run a docker-compose file for installation. Then everything can be done from the UI.
But, there are some prerequisites we need to follow....
Setting up an EC2 instance.
Our mail goal here would be to create an EC2 instance with sufficient RAM & necessary open ports on the server.
Note: For best experience it is recommended to use atleast 4GB RAM, that's why we are using
t3.medium
here.
For the creation of the instance I'm using terraform.
You can find the full source code from here.
First of all for our server we need a few necessary ports to be opened.
We can create a security group for this requirement like this...
resource "aws_security_group" "mailcow-security-group" {
name = "mailcow-sg"
description = "Allow TLS inbound traffic"
ingress {
description = "SSH"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "SSH"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "SSH"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "SSH"
from_port = 25
to_port = 25
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "SSH"
from_port = 465
to_port = 465
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "SSH"
from_port = 587
to_port = 587
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "SSH"
from_port = 143
to_port = 143
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "SSH"
from_port = 993
to_port = 993
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "SSH"
from_port = 110
to_port = 110
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "SSH"
from_port = 995
to_port = 995
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "SSH"
from_port = 4190
to_port = 4190
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "mailcow-sg"
}
}
Now, we can create a EC2 instance with the Ubuntu image with the above security group, like this...
resource "aws_instance" "mailcow_server" {
ami = "ami-0e35ddab05955cf57"
instance_type = "t3.medium"
key_name = aws_key_pair.deployer.key_name
vpc_security_group_ids = [aws_security_group.mailcow-security-group.id]
user_data = file("${path.module}/install-script.sh")
tags = {
Name = "mailcow-vm"
}
}
During the creation of the EC2 instance we are including an install script for installing Docker & Docker compose.
Here is the script....
#!/bin/bash
echo "Starting Docker Install" >> "/home/ubuntu/install-logs"
# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
# Add the repository to Apt sources:
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
echo "Completed Docker Install" >> "/home/ubuntu/install-logs"
echo "Manage Docker as a non-root user" >> "/home/ubuntu/install-logs"
sudo usermod -aG docker $USER
newgrp docker
Now, that we have setup the EC2 instance as needed, let's move on to our next requirements....
Setting up an DNS for the mail server
First of all we need a domain for this step, you can buy it from anywhere like namecheap or godady
I've bought my domain from namecheap, and added my domain to cloudflare for managing various DNS records.
For the basic mailserver we need atleast two DNS records - MX Record, A Record.
MX Record
A MX Record is basically used to point DNS servers where your domain is located.
Suppose someone trying to send a mail from a@gmail.com to your domain user@yoursite.com.
Now, the problem is how gmail would figure out where this user@yoursite.com is located. So gmail would the DNS server a MX record for yoursite.com.
We generally add a MX record for your domain to mail.yourdomain.com
For example, if you have a domain yousite.com
You would add a MX Record, like this
@ -> mail.yoursite.com
A Record
An A Record, is used to tell the DNS Servers where your site is.
Following the given example above, after adding the MX Record, you would need to add the A Record like this
mail.yourdomain.com -> IP Address
Now that we are done with the server configs, let dive in with the actual setup...
Installing Mailcow
Now that we have setup our server properly, for installing mailcow we need to just basically download the git repo of the mailcow and run the docker image...
Let's see, how can we do that...
Verify current user to have proper privilage
Before cloning the git repo, first we have to check if we have proper user privileges
Run this command
umask
The output of the above command should be 0022
.
For EC2 instance, by default the output should not be as expected. We need to switch the user to root, for this to work.
You can do that by running
sudo su
Clone mailcow repo
Let's clone to the repo, into /opt
directory and generate the configurations.
$ cd /opt
$ git clone git clone https://github.com/mailcow/mailcow-dockerized
$ cd mailcow-dockerized
$ ./generate_config.sh
Note: If you wan to change any of the configuration, you can edit the file
mailcow.conf
Run the image
Now that we have setup all the things, let's run the docker compose in the background, and hope everything is working properly.
docker compose up -d
After the running the image, visit the domain you have setup with the A record. Like in our example we will open yourdomain.com
Now that you have installed mailcow, you manage various admin tasks from the UI.
Few links for managing mailcow
Admin portal can be accessed via /admin
. The default username/password is admin/moohoo
.
All Department portal can be accessed via /department
. The default username/password is department/moohoo
.