warning: this guide is mostly obsolete although it contains a few tricks to make Postfix happy-happy in a container
The container is ideally data-independend and possibly load-balance-ready. So the shared Docker volume may also be some NFS or shared disk fs on the Docker host side.
Note that the Docker network 172.17.0.0/16 is added to mynetworks
so e.g. the RBL checks won’t be performed against the MX server itself.
You should advertise a public A record, not a CNAME, corresponding to the MX record. See the Postfix guide for further ado. As for Dockerization, this A record should correspond to container’s postfix myhostname
, not Docker host’s.
We are going to setup an MX inside a container, therefore the smtp port needs to be freed from the Docker host. You should see e.g. that port 25 and possibly 587 are taken,
netstat -antpe --inet --inet6|grep LISTEN|egrep '25|587'
tweak Postfix on the Docker host so it does NOT listen on ports 25 and 587,
vi /etc/postfix/master.cf (comment out port 25 and eventually port 587) postfix reload
and check again so ports 25 and 587 are now available.
Note. The system hostname doesn’t really matter (what really matters is postfix myhostname
), but for consistency I used the same (as short form) on the docker host. As for the container, its name generates its system hostname and static resolution anyway, which is okay.
The Docker host should differentiate itself from the container to be able to talk to it and avoid loops. The most simple and clean fix is to:
myhostname
on the Docker host versus the container (e.g. real machine name like xc
versus public service name like mx
),myorigin = $mydomain
on the Docker host,use the internal container IP as relayhost
on the Docker host (sorry but Postfix does not give a shit about system’s static name resolution).
vi /etc/aliases newaliases
vi /etc/postfix.main.cf
myhostname = xc.nethence.com myorigin = $mydomain relayhost = 172.17.0.3
service postfix restart
Ref. relayhost
maintained brutally
As a preview, the container to be setup will have something like,
myhostname = mx.nethence.com myorigin = $mydomain mydestination = $mydomain
Once the thing is up and running you should pass this acceptance tests,
send a mail to root@domain from the public network send a mail to root@domain from the docker host as wheeled user send a mail to root@domain from the docker host as root send a mail to root@domain from the container
Make sure that either the shared volume doesn’t or does exist whether it’s a fresh installation or not,
sudo ls -alhF /data/postfixprod/
Run the container based on the custom Ubuntu Docker image,
app=postfixprod docker ps -a | grep $app docker run -d --name $app -h $app -p 25:25 -p 587:587 -p 143:143 -v /data/$app:/home custom/ubuntu docker exec -ti postfixprod bash
Note. in case you need to link some Postfix & Dovecot mappings to MariaDB,
--link mariadbprod:mariadb \
Note. (optional) also if you like to,
-v /data/postfixprod.conf:/etc/postfix \ -v /data/postfixprod.spool:/var/spool/postfix \
And proceed with the Postfix guide.
This avoids storage of the logs on the image when commiting and also allows fail2ban to read those from the Docker host.
I am keeping the default rsyslog setup,
#cd /etc/rsyslog.d/ #cp -pi 50-default.conf 50-default.conf.dist #cp -pi postfix.conf postfix.conf.dist
but I’m moving the whole /var/log/
folder to the mounted volume,
pkill rsyslog mv /var/log/ /home/ ln -s ../home/log /var/log rsyslogd
To check that everything’s working,
tail -F /var/log/mail.log & cat /etc/rsyslog.d/postfix.conf ls -lhF /var/spool/postfix/dev/log logger -u /var/spool/postfix/dev/log --socket-errors=on test
Write the new init for the Docker container to start the daemons,
cd /home/ cat > init.bash <<-EOF #!/bin/bash /usr/sbin/rsyslogd cp -pf /etc/resolv.conf /etc/hosts /etc/services /var/spool/postfix/etc/ cp -pf /lib/x86_64-linux-gnu/libnss_* /var/spool/postfix/lib/x86_64-linux-gnu/ #/usr/lib/postfix/sbin/master -w /usr/sbin/postfix start /usr/sbin/dovecot tail -F /var/log/mail.err & while true; do sleep 120; done EOF chmod +x init.bash
and switch to it,
pkill rsyslogd postfix stop [[ ! -f /var/log/mail.err ]] && touch /var/log/mail.err /root/init.bash
Note. master
without -w
if you want it to be the remaining process of the container’s entrypoint (CMD). See man 8 master
. I prefer to use a fake init or just tail -F as a remaining process so I can eventually really restart the postfix daemon while keeping the container up and running (e.g. for dovecot).
See the the Postfix guide again to run some checks.
You should see Mounts and no Volumes,
docker volume ls docker inspect postfixprod|less /Mounts --> should show e.g. /data/postfixprod:/home /Volumes --> should be none
Everything’s fine? Then commit to an image from the Docker host and change the entrypoint against the new init,
docker commit --change='CMD ["/home/init.bash"]' -p postfixprod postfixprod.`date +%s`
or if the good entrypoint already has been applied, simply,
docker commit -p postfixprod postfixprod.`date +%s`
and check the image size (should be between 400 and 700MB, no more as we’re not including the data/Maildirs nor the logs in it),
docker images|grep postfixprod
You don’t need to swith to that new container right now, as it’s already up and running. But considering the user db could be kept outside the container (TODO), you would then be able to launch it on other nodes with that special init and just pointing to the same user db and mail storage!
app=postfixprod docker ps -a | grep $app docker run -d --name $app -h $app -p 25:25 -p 587:587 -p 143:143 -v /data/$app:/home `docker images|grep postfixprod|head -1|awk '{print $1}'` docker logs $app docker exec -ti $app ps auxfw #docker exec -ti $app bash
Finally add some helper to track the MX activity from the Docker host,
vi ~/.screenrc screen -t "postfixprod" 3 docker exec -ti postfixprod tail -F /var/log/mail.err /var/log/mail.log
and start the container when the host comes up,
vi /etc/rc.local echo -n starting postfixprod container and forcing dovecot... /usr/bin/docker start postfixprod \ && /usr/bin/docker exec -d postfixprod /usr/sbin/dovecot \ && echo done
Configure the System D service,
cd /etc/systemd/system vi postfixprod.service [Unit] Description=postfixprod container Requires=docker.service After=docker.service [Service] Restart=always ExecStart=/usr/bin/docker start -a postfixprod ExecStop=/usr/bin/docker stop -t 30 postfixprod [Install] WantedBy=default.target systemctl daemon-reload
Eventually restart the shit using System D now,
#docker stop postfixprod #systemctl start postfixprod #systemctl status postfixprod
Enable the shit at startup,
systemctl enable postfixprod systemctl status postfixprod
Ref. https://docs.docker.com/engine/admin/host_integration/