Tom Butler's programming blog

How to get sendmail working in docker PHP

Sending emails from PHP in a docker container

This should be a trivial task but there are several issues that trip you up when trying to get this to work.

I'm using the official php image php:7-latest as the base.

Normally, you'd expect to need to just install sendmail in the Dockerfile:

RUN apt-get update && \
    
apt-get install -\
        
sendmail
# And clean up the image
RUN rm -rf /var/lib/apt/lists/*

However, this doesn't work for several reasons and needs some additional tweaks.

Problem 1: PHP does not have the sendmail path set

This is an easy fix. Set sendmail_path in an ini file during the container build process:

RUN echo "sendmail_path=/usr/sbin/sendmail -t -i" >> /usr/local/etc/php/conf.d/sendmail.ini

Problem 2: You need to start the sendmail service

Because RUN commands only execute during the build process, you can't just add RUN service sendmail start to the Dockerfile, it has to be done each time the container is started.

So what you need to do is manually run the command in the container after it's been started:

docker exec [container idservice sendmail start

Problem 3: sendmail requires editing the /etc/hosts file

The solution above still won't work because sendmail scans the hostname for the local domain name. Docker doesn't set this by default, it can be generated by running this inside the container then restarting the sendmail service:

echo "$(hostname -i)\t$(hostname) $(hostname).localhost" >> /etc/hosts
service sendmail restart

With both these done your PHP scripts can send emails with the mail function again!

Better solution?

The obvious issue with this approach is that any time the contianer is restarted or run on a different machine you have to manually run both these commands in the container:

echo "$(hostname -i)\t$(hostname) $(hostname).localhost" >> /etc/hosts
service sendmail start

It would be better if this was done automatically in the Dockerfile.

This is possible by adding the lines to PHP's docker entrypoint during the build process:

RUN sed -'/#!\/bin\/sh/aservice sendmail restart' /usr/local/bin/docker-php-entrypoint
RUN sed -'/#!\/bin\/sh/aecho "$(hostname -i)\t$(hostname) $(hostname).localhost" >> /etc/hosts' /usr/local/bin/docker-php-entrypoint

Now you can just docker run or docker-compose up like normal without needing to remember to amend the hosts file and start the service.

Complete Dockerfile example

Here's a sample Dockerfile with a few extensions enabled as well:

FROM php:7-fpm

RUN apt-get update && \
    
apt-get install -\
        
zlib1g-dev libzip-dev sendmail


RUN echo "sendmail_path=/usr/sbin/sendmail -t -i" >> /usr/local/etc/php/conf.d/sendmail.ini 

RUN docker-php-ext-install pdo_mysql
RUN docker-php-ext-install zip

RUN sed -'/#!\/bin\/sh/aservice sendmail restart' /usr/local/bin/docker-php-entrypoint
RUN sed -'/#!\/bin\/sh/aecho "$(hostname -i)\t$(hostname) $(hostname).localhost" >> /etc/hosts' /usr/local/bin/docker-php-entrypoint

# And clean up the image
RUN rm -rf /var/lib/apt/lists/*

Caveats

This approach works and it's the best I was able to manage but there are a few notes when doing this:

1. This isn't a very Docker way of doing things. It would be nicer to run sendmail in its own container and connect to it from PHP, though this would require extra steps and still installing the bridge (e.g. ssmpt or similar) in the PHP container.

2. This solution is also rather brittle. If the base PHP image changes the location of the entrypoint script, this will stop working.

3. All the normal discussions about the pros/cons of sendmail vs a proper mailserver apply.