June 27, 2019

Docker: Postfix Mailing

Some of you may know, I've been in the process of containerizing many of my services in my 'free' time. This is the first post, of what I hope to be many more, on the migration of services – specifically, Postfix this go around.

It should be noted that this is a work in progress. Please don't use this in production. <Insert other IANAL stuff here>.

When I started looking at containerizing services by first step was to start looking for prior art to either use out-of-the-box or modify to suit my needs. I really didn't see anything that sparked my interest in hub.docker.com or a (very) quick github.com search.

After a few sorry attempts to roll my own completely from scratch I decided to look more closely into the linuxserver.io image build process.

So far I am quiet content.

What I really want(ed) is a good CI/CD implementation to build my containers through my private Jenkins implementation and upon successful build, deploy the container to my infrastructure.

Full disclosure: I haven't reached nirvana yet.

As I eluded to, the first "service" I chose to containerize was my postfix setup. I use postfix, in this instance, to relay e-mail out to the rest of the world. My ISP actually filters port 25 out and I have access to other systems which can do that work for me already.

My requirements are:

  • A single egress point from the home network for e-maill traffic
  • Must be able to specify a 'relay_host' to funnel all mail through
  • Random applications need to be able to send e-mail
  • (Eventually) Be easy to build

Let's get started...

If all you want to do is run a basic Postfix server that can relay mail it should be easy enough to just run my container. I wrote a small bash script to build the container.


docker create \
  --name=tkf-postfix \
  -e PUID=11 \
  -e PGID=952 \
  -e TZ=US/Mountain \
  -e TKF_MAILDOMAIN=copperdale.teknofile.net \
  -e TKF_RELAY_HOST="$(cat ./tkf.relayhost)" \
  -e TKF_MYNETWORKS="$(cat ./tkf.mynetworks)" \
  -e TKF_RELAY_PASSWORD="$(cat ./relay_password)" \
  -e TKF_MYHOSTNAME="$(cat ./tkf.myhostname)" \
  -v /mnt/usb/docker/tkf-postfix/:/config \
  -p 25:25 \
  --restart unless-stopped \
  --net=tkfdocker \

Let's break down the parameters being passed to docker create:

  • -e PUID/PGID - Used to map internal container UID/GID to external (host) UID.  For more info: https://medium.com/@mccode/understanding-how-uid-and-gid-work-in-docker-containers-c37a01d01cf
  • -e TZ - The timezone you want to set to inside the container, I'm in the US/Denver area so I like times to make sense to me at a quick glance
  • -e TKF_MAILDOMAIN - In the postfix configuration, this is set smtpd_banner and smtp_helo_name
  • -e TKF_RELAY_HOST - This is the hostname of the system you wish to relay mail through. This sets the relayhost parameter within postfix. The "$(cat ...)" syntax you see here reads the contents of the file to populate the variable, it keeps some of my specific configuration out of github
  • -e TKF_MYNETWORKS - Sets the postfix mynetworks parameter. This should be set to the IPv4/IPv6 network addresses you want to accept mail for. This is your internal IP networks (i.e.
  • -e TKF_RELAY_PASSWORD - This is where things may get a little ugly. My relay host requires authentication. Because of that, I set this variable to: "my.mail.relay.example.org MYUSERNAME:MYPASSWORD". When the container starts up, it will take this variable and put it in /config/relay_passwords, then run the 'postmap' command on the relay_passwords file, changes the permissions of the .db file that postmap created and cleans up the original relay_passwords file itself
  • -e TKF_MYHOSTNAME - Sets the postfix myhostname variable
  • -v /mnt/usb/docker/tkf-postfix/:/config - On this line, we are presenting the local path (in my case /mnt/usb/docker/tkf-postfix) to be mounted within the container at the location of /config. This should be optional, but as this still a 0.0.1 release, I want to watch the configs in a little bit easier method
  • -p 25:25 - This parameter exposes the host TCP port 25 and presents the traffic to the container on the container's port 25
  • --restart until-stopped - https://blog.codeship.com/ensuring-containers-are-always-running-with-dockers-restart-policy/
  • --net=tkf-docker - I have an internal docker network that I wanted to expose this container to. This is probably optional for you too
  • teknofile/tkf-docker-postfix:devel - the image that we're going to pull down from hub.docker.com

And that's it! Well, it should be anyways. Truth be told, since I started working on this container image I have since switched from a username/password authentication model to a certificate based authentication model to allow for relay access. I'll be updating the image (and this blog post) when I merge those changes in.

At this point you should be up and running with a container that can relay mail for you.

But wait! There's more!

More technical details at least...

As I mentioned previouslly, I decided to base my containers off of the linuxserver.io container base. They have done a lot of the hardware for me. Things I like about them:

  • They build multi-arch (x86/ARM) images via Jenkins (Disclaimer - i am only building x86 right now)
  • Images are based off of a small alpine base image
  • The s6-overlay system, IMHO, makes building the image a breeze

The bulk of the 'work' in the container happens when we COPY root/ / int he Dockerfile. If you look at the root/etc/cont-init.d/45-postfix file what you'll see is:

  • /config/main.cf we'll bootstrap a basic postfix configuration
  • Then we'll configure some basic postfix items like biff, smtp_always_send_ehlo, append_dot_mydomain and the like. Nothing too fancy
  • Next we'll setup a /etc/aliases files. Not really used now but it cleaned up some warnings
  • We setup the relayhost parameter and set relay restrictions to only allow networks defined in mynetworks
  • There are some TLS pieces setup (that still need more testing, in case anyone is interested)
  • Some SASL authentication bits are setup, but also needs to be tested