I've been making more changes to my form API backend recently, migrating the deployment environment from FreeBSD jails to Docker containers. This isn't a post about that migration though. In the process of migrating I lost access to a fully featured sendmail binary.
If you'd like to see why sendmail specifically is so important to me, feel free to check out this talk I gave last year where I go through why I wrote the API in the first place, and how I tested it (which involves sendmail).
In Alpine Linux, which is what I use for
formulate's base docker image1, sendmail is a part of
busybox2. This means limited functionality for that version of sendmail, and this affected its ability to relay mail from within the docker container. When I tried using the built-in
sendmail I was seeing a message similar to the following:
sendmail: can't connect to remote host (127.0.0.1): Connection refused
The gist of that message is that sendmail is trying to send mail by connecting to localhost, which it's treating as a remote mail server. But there is no mail server listening on localhost.
One thing I could have done was install a separate application to handle relaying mail in the container. In fact, in my FreeBSD jail that is exactly what I did. The
sendmail binary exists but does not get used. Instead, I configured
opensmtpd to handle mail serving duties. I didn't want my docker image any bigger though, it was already
10.5 MB and I was hoping to get to single digit sizes.3
I found out that instead of a configuration file I could use an environment variable—
SMTPHOST—to point to an actual mail server to handle messages. That allowed me to use docker to set this environment variable and get form submissions sending via email again.
docker run -it --rm -e SMTPHOST="mail.host.domain" -p 80:8000 formulate
Used as the base docker image primarily for its smaller attack surface.
BusyBox combines tiny versions of many common UNIX utilities into a single small executable.
I actually started off at
14.4 MB but was able to shave some size off the rust binary by following some tips for creating smaller rust executables from the min-sized-rust repository.