Free Mail Server on OCI

Free Mail Server on OCI – Docker, Cron, Certbot

Spread the love

This is the series of articles about how to setup free own mailserver on OCI.

Docker

Our Mail Server is based on the Dockerized Container Image Docker-Mailserver, documentation is here.

Let’s install Docker and Docker Compose to the VM instance, run the following commands:

# Source: https://docs.docker.com/engine/install/debian/
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 "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
# Install Docker Packeges
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Check that installation was successful:

ubuntu@mailserver:/$ docker -v
Docker version 25.0.3, build 4debf41
ubuntu@mailserver:/$ docker compose version
Docker Compose version v2.24.6

Docker Compose

Certbot

For successfull launch and work of mailserver the HTTPS certificates needs to be generated. I will be using Certbot, since it is very easy to use. When certbot generate new HTTPS certificates, they needs to be validated and verified against the hostname for which it’s been generated. I’m using Cloudflare as my DNS provider for my domain names, so I will be using certbot-cloudflare docker images which automatically validates the HTTPS certificates using the special DNS record.

Create a compose.yaml file anywhere on your local machine with the following content:

services:
  # certbot generate SSL certs
  certbot-cloudflare:
    image: certbot/dns-cloudflare:latest
    command: certonly --dns-cloudflare --dns-cloudflare-credentials /run/secrets/cloudflare.ini -d mail.mymail.com
    volumes:
      - /data/certbot/certs/:/etc/letsencrypt/
      - /data/certbot/logs/:/var/log/letsencrypt/
    secrets:
      - source: cloudflare
        target: cloudflare.ini

  # certbot renew SSL certs
  certbot-cloudflare-renew:
    image: certbot/dns-cloudflare:latest
    command: renew --reuse-key --dns-cloudflare --dns-cloudflare-credentials /run/secrets/cloudflare.ini
    volumes:
      - /data/certbot/certs/:/etc/letsencrypt/
      - /data/certbot/logs/:/var/log/letsencrypt/
    secrets:
      - source: cloudflare
        target: cloudflare.ini

secrets:
  cloudflare:
    file: /data/secrets/cloudflare.ini

In this file we defined 2 Docker Compose services. The 1st one (certbot-cloudflare) will generate certificate files, this needs to be run only once. Certificate files will be stored in /data/certbot/certs/ folder, certbot log files will be stored in /data/certbot/logs/ folder. Don’t worry, those folders will be created automatically by Docker Compose when you run any of those services. The 2nd one (certbot-cloudflare-renew) will renew private certificate file without regenerating the public key (–reuse-key parameter), this allows you to keep the DANE TLSA DNS record of your domain name unchanged when the new certificates generated.

Don’t forget to replace mail.mymail.com by your mailserver hostname.

In my case, since I’m using cloudflare, I need to create a secret file cloudflare.ini which will contain the cloudflare API token (can be found in cloudflare account), and upload it to /data/secrets/cloudflare.ini, the content of this file will look like (don’t forget to replace the token by yours one):

dns_cloudflare_api_token=abcdAaAa-1ABCDabc12aABabABCabcAaAa1ABCDa

Upload cloudflare.ini file to mailserver /data/secrets/ folder, so it looks like:

ubuntu@mailserver:/data/secrets$ ls -al
total 9
drwxr-xr-x 2 root root 3896 Mar 23 15:49 .
drwxr-xr-x 3 root root 3896 Mar 23 15:47 ..
-rw------- 1 root root   65 Mar 23 15:23 cloudflare.ini

To upload the file to mailserver you can use WinSCP for Windows, Filezilla for Linux/Mac or any other software which is able to trasfer files over SSH connection using the private SSH key.

Same way upload compose.yaml file to mailserver /data/docker/ folder, so it looks like:

ubuntu@mailserver:/data/docker$ ls -al
total 11
drwxr-xr-x 2 root root 3896 Mar 23 15:58 .
drwxr-xr-x 4 root root 3896 Mar 23 15:54 ..
-rw-rw-rw- 1 root root 2086 Mar 23 15:57 compose.yaml

Before you run the following command, if you also use Cloudflare as your DNS server, make sure that you have created a respective hostname, in my examples it’s mail.mymail.com.

Now it’s time to generate certificate files for the domain name for the first time, run the following command being logged in to your mailserver via ssh:

sudo docker compose -f /data/docker/compose.yaml run --rm certbot-cloudflare

Follow the instructions prompted and provide the details requested, the result should be like the following:

ubuntu@mailserver:/data/docker$ sudo docker compose -f /data/docker/compose.yaml run --rm certbot-cloudflare
[+] Creating 1/1
 ✔ Network docker_default  Created                                                                                                                                                                                      0.1s 
[+] Running 15/15
 ✔ certbot-cloudflare 14 layers [⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿]      0B/0B      Pulled                                                                                                                                                 4.5s 
   ✔ c6b39de5b339 Pull complete                                                                                                                                                                                         0.4s 
   ✔ 1acedc820fdf Pull complete                                                                                                                                                                                         0.3s 
   ✔ 180c7df79327 Pull complete                                                                                                                                                                                         0.4s 
   ✔ 75b3f9577ce1 Pull complete                                                                                                                                                                                         0.6s 
   ✔ 96f192c65cea Pull complete                                                                                                                                                                                         0.7s 
   ✔ 911b6c6c91b7 Pull complete                                                                                                                                                                                         0.7s 
   ✔ a71c51a8a49a Pull complete                                                                                                                                                                                         0.9s 
   ✔ a01d09153d58 Pull complete                                                                                                                                                                                         1.0s 
   ✔ 84f1782876bc Pull complete                                                                                                                                                                                         1.0s 
   ✔ 42a97c425f04 Pull complete                                                                                                                                                                                         1.2s 
   ✔ 2c4d8ca8e06c Pull complete                                                                                                                                                                                         1.4s 
   ✔ 815a76aa46d1 Pull complete                                                                                                                                                                                         1.4s 
   ✔ 19b2b2a0e7e7 Pull complete                                                                                                                                                                                         1.5s 
   ✔ ea0118c4ea0c Pull complete                                                                                                                                                                                         1.8s 
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Enter email address (used for urgent renewal and security notices)
 (Enter 'c' to cancel): [email protected]

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.3-September-21-2022.pdf. You must
agree in order to register with the ACME server. Do you agree?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: Y

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Would you be willing, once your first certificate is successfully issued, to
share your email address with the Electronic Frontier Foundation, a founding
partner of the Let's Encrypt project and the non-profit organization that
develops Certbot? We'd like to send you email about our work encrypting the web,
EFF news, campaigns, and ways to support digital freedom.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: N
Account registered.
Requesting a certificate for mail.mymail.com
Waiting 10 seconds for DNS changes to propagate

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/mail.mymail.com/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/mail.mymail.com/privkey.pem
This certificate expires on 2024-06-21.
These files will be updated when the certificate renews.

NEXT STEPS:
- The certificate will need to be renewed before it expires. Certbot can automatically renew the certificate in the background, but you may need to take steps to enable that functionality. See https://certbot.org/renewal-setup for instructions.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
 * Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
 * Donating to EFF:                    https://eff.org/donate-le
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Check the certificate files are stored in /data/certbot/certs/ folder:

root@mailserver:/data/certbot/certs/live/mail.mymail.com# ls -al
total 9
drwxr-xr-x 2 root root 3896 Mar 23 17:04 .
drwx------ 3 root root 3896 Mar 23 17:04 ..
-rw-r--r-- 1 root root  692 Mar 23 17:04 README
lrwxrwxrwx 1 root root   46 Mar 23 17:04 cert.pem -> ../../archive/mail.mymail.com/cert1.pem
lrwxrwxrwx 1 root root   47 Mar 23 17:04 chain.pem -> ../../archive/mail.mymail.com/chain1.pem
lrwxrwxrwx 1 root root   51 Mar 23 17:04 fullchain.pem -> ../../archive/mail.mymail.com/fullchain1.pem
lrwxrwxrwx 1 root root   49 Mar 23 17:04 privkey.pem -> ../../archive/mail.mymail.com/privkey1.pem

Now we need to add certbot-cloudflare-renew to cron job of the virtual machine so the renew will happen automatically at 0:00 on the day when it’s needed.

If cron is not installed on your machine (you can check it by running the command sudo systemctl status cron), install it by running the following command:

sudo apt update -y && sudo apt install cron

And enable it (cron might be enabled already, but just double check) so after reboot it will be still running:

sudo systemctl enable cron

Then run the following command:

sudo crontab -e

And insert the following line at the end of the crontab file shown in your default editor (in my case it is nano):

1 1 * * * docker compose -f /data/docker/compose.yaml run --rm -d certbot-cloudflare-renew >/dev/null 2>&1

So the crontab file will look like:

# Edit this file to introduce tasks to be run by cron.
# 
# Each task to run has to be defined through a single line
# indicating with different fields when the task will be run
# and what command to run for the task
# 
# To define the time you can provide concrete values for
# minute (m), hour (h), day of month (dom), month (mon),
# and day of week (dow) or use '*' in these fields (for 'any').
# 
# Notice that tasks will be started based on the cron's system
# daemon's notion of time and timezones.
# 
# Output of the crontab jobs (including errors) is sent through
# email to the user the crontab file belongs to (unless redirected).
# 
# For example, you can run a backup of all your user accounts
# at 5 a.m every week with:
# 0 5 * * 1 tar -zcf /var/backups/home.tgz /home/
# 
# For more information see the manual pages of crontab(5) and cron(8)
# 
# m h  dom mon dow   command
1 1 * * * docker compose -f /data/docker/compose.yaml run --rm -d certbot-cloudflare-renew >/dev/null 2>&1

This will add a cron job to be running every day on 01:01, under root user (since we run sudo crontab -e).

You can also try to run the renew certbot manuall to check whether it is working as expected:

sudo docker compose -f /data/docker/compose.yaml run --rm certbot-cloudflare-renew

You should see something like:

ubuntu@mailserver:/$ sudo docker compose -f /data/docker/compose.yaml run --rm certbot-cloudflare-renew
Saving debug log to /var/log/letsencrypt/letsencrypt.log

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/mail.mymail.com.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Certificate not yet due for renewal

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
The following certificates are not due for renewal yet:
  /etc/letsencrypt/live/mail.mymail.com/fullchain.pem expires on 2024-06-21 (skipped)
No renewals were attempted.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

This means all good, no need to renew certificates yet.

To check whether the cron is executing the job as expected run the following command and have a look a t the bottom logs (few lines):

sudo service cron status

You should see something like the following:

ubuntu@mailserver:/$ sudo service cron status
● cron.service - Regular background program processing daemon
     Loaded: loaded (/lib/systemd/system/cron.service; enabled; vendor preset: enabled)
     Active: active (running) since Sun 2024-03-02 02:33:41 UTC; 3 weeks 2 days ago
       Docs: man:cron(8)
   Main PID: 1749 (cron)
      Tasks: 1 (limit: 6973)
     Memory: 996.0K
        CPU: 8.989s
     CGroup: /system.slice/cron.service
             └─1749 /usr/sbin/cron -f -P

Mar 02 01:01:01 mailserver CRON[2190693]: pam_unix(cron:session): session closed for user root
Mar 02 01:01:01 mailserver CRON[2190988]: pam_unix(cron:session): session opened for user root(uid=0) by (uid=0)
Mar 02 01:01:01 mailserver CRON[2190989]: (root) CMD (docker compose -f /data/docker/compose.yaml run --rm -d certbot-cloudflare-renew)
Mar 02 01:01:03 mailserver CRON[2190988]: (CRON) info (No MTA installed, discarding output)
Mar 02 01:01:03 mailserver CRON[2190988]: pam_unix(cron:session): session closed for user root

Let’s configure Mail Server itself, finally, follow this article.