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)
AuthUser=postmaster@mycompany.biz
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 ssmtp.conf
.
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 docker0
interface:
# 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 mailhub
.
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 mynetwork
option:
# 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
To configure ssmtp
, just point the mailhub
to localhost
.
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.
Hi,
how is possible to manage the authentication on an external postfix relay to permit it to allow connections only from specific containers located in a many external clusters without hardcode a cluster node ips?
thank in advance
Hi Antonio,
Without hard coding IPs: I think you can either supply Postfix auth (username/password) to the containers that are allowed to send emails. Alternatively, you can make use of Docker networking and add the containers to an additional network that contains the mail server. In this way, the mail server is isolated from the other network and not-allowed containers will not be able to network with the mail server.