I packaged a standard application (think of it as a standard PHP or <insert your preferred framework here>) into a Docker container. So far, it was working flawlessly, but then a problem arose: send an email from the Docker container (the event is triggered within the container).
As you may know, a good Docker container is a container with only one process running: the naive solution for our case would be to have, in addition to having our PHP process running, another process to manage the email interexchange (an MTA, i.e. Postfix). As we are following the best practices for Docker containers, this path is discouraged.
There are many solutions to this problem.
The common ground for all of the solutions is to rely on
ssmtp when sending emails from the container.
ssmtp is a simple relayer to deliver local emails to a remote mailhub that will take care of delivering the emails.
Provided that the container distribution ships
ssmtp, the installation is straightforward: just add the package during the install phase of the Dockerfile.
ssmtp must be configured to relay every email an SMTP host, e.g.:
# cat /etc/ssmtp/ssmtp.conf # The user that gets all the mails (UID < 1000, usually the admin) root=postmaster # The place where the mail goes. The actual machine name is required # no MX records are consulted. Commonly mailhosts are named mail.domain.com # The example will fit if you are in domain.com and you mailhub is so named. # Use SSL/TLS before starting negotiation UseTLS=Yes UseSTARTTLS=Yes # Fill the following with your credentials (if requested) AuthUserfirstname.lastname@example.org AuthPass=supersecretpassword # Change or uncomment the following only if you know what you are doing # Where will the mail seem to come from? # rewriteDomain=localhost # The full hostname # hostname="localhost" # The address where the mail appears to come from for user authentication. # rewriteDomain=localhost # Email 'From header's can override the default domain? # FromLineOverride=yes
All the three solutions that I am going to illustrate rely on having a custom
mailhub that must be configured accordingly.
Let’s review each solution.
An external SMTP relay host
If an external SMTP relay host is available, the solution is to point
mailhub option of
ssmtp to the external SMTP host.
Another container running the MTA
The proper way to solving this problem would be to run a Docker container just for the MTA itself (personal preference: Postfix). One caveat of this solution: some Linux distributions come with an MTA running out of the box. If the container host is already running an MTA, the container cannot publish the port
25/tcp from the Postfix container [the address is already in use by the MTA running on the host].
By searching on GitHub, a promising and an up-to-date container is the
eea.docker.postfix. After you deploy the Postfix container, link every container that needs an MTA to it. E.g.
# docker run --link=postfix-container my-awesome-app-that-needs-an-mta
The container must configure
ssmtp to use
postfix-container(or the name defined as the
link) in the
mailhub option in
Relying on the host MTA
Premise: the Docker daemon exposes an adapter to all the containers running on the same host. This adapter is usually named as the
# ip a show docker0 5: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 11:22:33:44:55:66 brd ff:ff:ff:ff:ff:ff inet 172.17.42.1/16 brd 172.17.255.255 scope global docker0 valid_lft forever preferred_lft forever inet6 fe80::11:22ff:fff0:3344/64 scope link valid_lft forever preferred_lft forever
If the host MTA is listening on the
docker0 interface, then the containers can relay email to the host MTA. There is not an extra configuration on the container itself, just configure
ssmtp to use the
docker0 IP as the
EXTRA: HOW TO CONFIGURE POSTFIX TO LISTEN ON DOCKER INTERFACE (LIKE DOCKER0) AS WELL
To use the solution described above, the MTA on the host must be configured to listen on the
docker0 inteface as well. In case that the MTA in case is Postfix, the configuration is straightforward:
On the host, open
/etc/postfix/main.cf and add the
docker0 IP to the
inet_interfaces option and add the subnetwork block range of the containers that need to use the host MTA to the
# cat /etc/postfix/main.cf [...] inet_interfaces = 127.0.0.1, 172.17.42.1 mynetworks = 127.0.0.0/8 172.17.42.0/24 [...]
If Postfix is set to be started at boot by systemd, we need to take care of the dependency: Docker daemon must be started before the Postfix daemon, as Postfix needs to bind on the
docker0 IP address.
In order to express this dependency, and luckily for us, systemd already ships with a service that detects when an interface is up:
# systemctl | grep docker0 sys-devices-virtual-net-docker0.device loaded active plugged /sys/devices/virtual/net/docker0
Postfix must be started after the
docker0 inteface has been brought up, and to express the dependency we must override Postfix’s service units (this may vary based on the host distribution):
# systemctl | grep postfix postfix.service loaded active exited Postfix Mail Transport Agent postfix@-.service loaded active running Postfix Mail Transport Agent (instance -)
in this case it is enough to override only the Postfix instance service with:
# systemctl edit postfix@-.service
Override the unit service file by declaring the dependency explicitely:
[Unit] Requires=sys-devices-virtual-net-docker0.device After=sys-devices-virtual-net-docker0.device
Reload systemd with
systemctl daemon-reload and restart Postfix with
systemctl restart postfix.
Relying on the host MTA by using
host network driver on Docker
When a container is set to use host networking interface, the container can access the host networking and thus its services. If the container host already has an MTA configured, then the containers can use it by just pointing to
localhost.The syntax to use host networking interface for the application that needs to use the host MTA is:
# docker run --net=host my-awesome-app-that-needs-an-mta
ssmtp, just point the
NOTE: Using the host networking interface has obviously security drawbacks, because containers do not have their networking containerized by Docker but rather rely on the host networking; this can guarantee to the Docker container to have access to the whole networking stack (in read-only mode) and open low-numbered ports like any other
root process. Use this networking option by carefully weigh pro and cons.