How to Provide SMTP Email Functionality for the Official Docker WordPress Image

The official Docker WordPress image is minimal. It does not have any MTA (Mail Transfer Agent) like Exim4 or Postfix installed by default. So you’ll be unable to send e-mails from your wordpress Docker container without using some external SMTP relay service.

This article describes several solutions for adding Postfix to your system. So you’ll be able to send e-mails from WordPress using SMTP.

Of course, this is a barebone article. It is very minimal. E.g. it does not consider security problems. You’ll need to check the Postfix documentation and security-related articles to secure the system. Here we consider only some minimal functionality to make your SMTP e-mail sending functionality work at all.

1. How to Set up an SMTP Email Server in Docker

1.1 How to Install Postfix in a Separate Container (without DKIM)

In this section, we’ll create a Postfix docker container based on the image tozd/postfix. This solution does not support DKIM verification (digital signature for your e-mails). If you need DKIM, please skip to the next section.

Let’s create the directory /opt/projects/mail:

mkdir /opt/projects/mail
cd /opt/projects/mail

We’ll need one more directory for our container:

mkdir volumes
mkdir volumes/spool
mkdir volumes/log

Now let’s add the file /opt/projects/mail/docker-compose.yml:

version: '3.8'

services:

  mail:
    container_name: mail-postfix
    image: tozd/postfix
    restart: always

    expose:
      - "25/tcp"
      - "465/tcp"
      - "587/tcp"

    environment:
      MAILNAME: example.com # replace with your own server name instead of example.com
      MY_NETWORKS: 172.17.0.0/16 127.0.0.0/8 192.168.0.0/16 # my mail server happened to be in the 192.168.0.0/16 so I had to add this network
      MY_DESTINATION: localhost.localdomain, localhost
      ROOT_ALIAS: admin@example.com # replace with your e-mail address

    volumes:
      - /opt/projects/mail/volumes/spool:/var/spool/postfix
      - /opt/projects/mail/volumes/log:/var/log/postfix

    networks:
      mail:

networks:
  mail:
    name: mail # or it will be created as mail_mail

Also it is a good idea to check the IP range (subnet) for the network mail. To do that, on the host machine run:

docker inspect mail

In my case the subnet was 192.168.128.0/20. So I have added 192.168.0.0/16 to the MY_NETWORKS variable to avoid problems with such networks. Without it I was getting the error “Relay access denied” when trying to send an e-mail from my WordPress container.

Another way is to explicitly set the subnet for the network mail. See the ipam section in the docker-compose documentation (ipam because IP Address Management driver is used). The file /opt/projects/mail/docker-compose.yml for this case could look like this:

version: '3.8'

services:

  mail:
    container_name: mail-postfix
    image: tozd/postfix
    restart: always

    expose:
      - "25/tcp"
      - "465/tcp"
      - "587/tcp"

    environment:
      MAILNAME: example.com # replace with your own server name instead of example.com
      MY_NETWORKS: 172.16.0.0/12 127.0.0.0/8 
      MY_DESTINATION: localhost.localdomain, localhost
      ROOT_ALIAS: admin@example.com # replace with your e-mail address

    volumes:
      - /opt/projects/mail/volumes/spool:/var/spool/postfix
      - /opt/projects/mail/volumes/log:/var/log/postfix

    networks:
      mail:
#        ipv4_address: 172.30.0.2 # there is no need to set the container IP in the network `mail` explicitly,
                                  # but you can do it if you wish

networks:
  mail:
    name: mail # or it will be created as mail_mail
    ipam:
      driver: default
      config:
        - subnet: "172.30.0.0/24"

I did not assign a static IP to the mail-postfix container in this example. You can do this if you wish. But please notice that at the time of writing you can not assign an IP to the subnet gateway in docker-compose version 3. It is unavailable in version 3 of docker-compose yet (available in version 2 though).

And one more way to deal with the network IP range assignment problem is to set explicitly the IP address pool in which Docker is allowed to create networks. This approach is described in this article. And it consists in setting the default address pool in the file /etc/docker/daemon.json.

Since the docker-compose.yml above creates the network mail, we should start the container before any other container which will join this network (e.g. before our WordPress container):

docker-compose up -d

To stop/remove the container we could use:

docker-compose down

1.2 How to Install Postfix in a Separate Container (with DKIM)

In this section, we’ll create a Postfix docker container based on the image catatnight/postfix. This image supports DKIM verification.

Let’s add the file /opt/projects/mail/docker-compose.yml:

version: '3.8'

services:

  mail:
    container_name: mail-postfix
    image: catatnight/postfix
    restart: always

    expose:
      - "25"

    environment:
      maildomain: wpdiaries.com # replace with your own server name instead of example.com
      smtp_user: admin:pwd # username and password for SMTP

    volumes:
      - /opt/projects/mail/volumes/domainkeys:/etc/opendkim/domainkeys
#      - /opt/projects/mail/volumes/log/supervisor:/var/log/supervisor

    networks:
      mail:
#        ipv4_address: 172.30.0.2

networks:
  mail:
    name: mail # or it will be created as mail_mail
    ipam:
      driver: default
      config:
        - subnet: "172.30.0.0/24" # 172.16.0.0 – 172.31.255.255

Run the container:

docker-compose up -d

Now we need to generate public and private keys for DKIM. The procedure of generating files (though not for a docker container) is described in detail in this article.

Let’s enter the running container terminal:

docker exec -it mail-postfix bash

Generate the keys in the directory /etc/opendkim/domainkeys inside the container (replace example.com with your mail domain name):

cd /etc/opendkim/domainkeys
sudo opendkim-genkey -s mail -d example.com
sudo chown opendkim:opendkim mail.private

-s defines a selector here. It could be any string. Often the current year is used as a selector. We used the word mail.

The command above will generate 2 files :
mail.txt – our public key
mail.private – our private key
and will change the owner for the private key file.

Exit the container mail-postfix:

exit

Now the file on our mounted (in docker-compose) volume /opt/projects/mail/volumes/domainkeys/mail.txt will contain something similar to this:

mail._domainkey	IN	TXT	( "v=DKIM1; k=rsa; "
	  "p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvhz766AiMeVPzidwF+wXzDM6z+wt4g8pOR2VoxPwa4wfBL3A/lqNopbCBMBpDB6mEDzDUmWMw8r6NjKRulBMqE5tq470nvaNR05w7MiM3RjrhSwVjPDMhA46HNOSSf26hTrtnMqpuNCIidGpH3W132h6sGtc6YuMTByKDZe77+QIDAQAB" )  ; ----- DKIM key mail for example.com

So we will have to add this TXT record with the following parameters on the DNS server for our mail domain:

Record type: TXT
Host: mail._domainkey.example.com
Value: v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvhz766AiMeVPzidwF+wXzDM6z+wt4g8pOR2VoxPwa4wfBL3A/lqNopbCBMBpDB6mEDzDUmWMw8r6NjKRulBMqE5tq470nvaNR05w7MiM3RjrhSwVjPDMhA46HNOSSf26hTrtnMqpuNCIidGpH3W132h6sGtc6YuMTByKDZe77+QIDAQAB

E.g. the DKIM TXT record for wpdiaries.com on the Namecheap DNS server looks like this:

DKIM TXT record on Namecheap DNS server
DKIM TXT record on Namecheap DNS server

Then we’ll need to restart our docker container mail-postfix:

cd /opt/projects/mail/
docker-compose restart

For more detailed information about adding a DKIM key (but without Docker) please see this article.

1.3 How to Add Postfix Directly to the WordPress Image

This section has been added for educational purposes only. So you can safely skip it if you wish. You shouldn’t add Postfix directly to the official wordpress image. Why? Because it is not recommended to have more than 1 responsibility for 1 docker container. Please see the section Decouple applications in the official documentation on Docker best practices.

So please do not use the approach described in this section. It is given here for educational purposes only.

Still, if you are interested in this subject, you could use a Dockerfile like this:

FROM wordpress:5.5.1-php7.4-apache

# Install packages under Debian
RUN apt-get update

# Install Postfix (replace wpdiaries.com with your own domain name)
RUN ["/bin/bash", "-c", "debconf-set-selections <<< \"postfix postfix/mailname string wpdiaries.com\""]
RUN ["/bin/bash", "-c", "debconf-set-selections <<< \"postfix postfix/main_mailer_type string 'Internet Site'\""]
RUN apt-get install --assume-yes postfix

# Uncomment the following line if you need inter-container communication on port 25
#EXPOSE 25

COPY entry.sh /
RUN chmod +x /entry.sh

ENTRYPOINT ["/entry.sh"]

Where entry.sh is a script like this:

#!/bin/bash

service postfix start

# Similar to how Apache2 is run in the official wordpress image Dockerfile:
/usr/local/bin/docker-entrypoint.sh apache2-foreground

Then in docker-compose.yml instead of the image directive you will need to use the build directive like this:

    build:
      context: .

Please notice that in our entry script entry.sh first we run Postfix and then we execute a command similar to the ENTRYPOINT directive of the official wordpress image Dockerfile. We do it like this because according to the official documentation on ENTRYPOINT and on CMD, if multiple such directives exist, only the last one is executed. And so, despite the wordpress image (on which this image has been based) already has an ENTRYPOINT directive, this first directive will be ignored. So we need the ENTRYPOINT command in our Dockerfile to execute all we need (in our case, to run Postfix) plus run a directive similar to the one from the official wordpress image Dockerfile.

For more information on Dockerfiles please see the Dockerfile reference.

Again, having Postfix right inside the wordpress container is not recommended. This section of the article is given for educational purposes only. Please see the sections above instead.

1.4 How to Send Emails Using Host Machine SMTP

Some people suggest configuring Postfix on the host machine. Then you could send e-mails from Docker using your host machine MTA (Mail Transfer Agent). This solution is discussed here.

The reason behind such a solution could be that you could possibly need some sending functionality on your host machine anyway (maybe some service or automatic system notification e-mails). In this case why set one more SMTP server in Docker.

But if you, like me, do not need to send e-mails from your host machine, then I would keep the SMTP functionality inside a Docker container.

Personally I prefer to install as much functionality as possible in Docker containers. It simplifies deployment really much. And looks a little bit cleaner to me. But the final decision is up to you of course.

2. How to Configure Docker WordPress Container to Use Postfix

In the previous sections, we’ve considered how to install Postfix in a separate docker container. In both scenarios, the container with Postfix creates the network mail.

Now we need to add the network mail to the docker-compose.yml files of our WordPress projects where we need the mail sending functionality :

version: '3.8'

services:

  wordpress:
    image: wordpress
    
    # More configuration options here
    # ...
    
    networks:
      mynetwork1:
      mynetwork2:
      # We are adding the network `mail` here:
      mail:


networks:
  mynetwork1:
  mynetwork2:
  # We are adding the network `mail` here:
  mail:
    external: true  # which also means this network must exist already before this container is run
    name: mail  # the name is nnecessary or the container will look for currentdirectoryname_mail

These WordPress containers will join the network mail. They do not create this network. So the network mail must exist before you start these WordPress containers (i.e. our Postfix mail container must start first).

3. How to Configure Mail in WordPress Admin Panel

In user-defined networks (we have the network mail here) automatic service discovery exists. It means that containers can communicate not only by an IP address but by a container name too. It is easy to check (it is better to run such a check in the Development environment). If we have some container connected to the mail network, e.g. with the name my-wordpress-container, we can log into bash of this my-wordpress-container like this:

docker exec -it my-wordpress-container bash

Then in the container bash we can install the necessary packages and run the ping command (the example is given for a container built on the official wordpress image which runs Debian):

apt-get update
apt-get install iputils-ping

ping mail-postfix

You’ll see that you can ping the container mail-postfix by its container name from another container running in the same network mail.

So, in our case, since the Postfix container name is mail-postfix, we’ll use this name instead of localhost in the mail configuration.

If you use e.g. Post SMTP plugin for mail configuration, the outgoing mail server configuration screen in the plugin settings could look like this:

Post SMTP outgoing mail server configuration
Post SMTP outgoing mail server configuration

Of course, we could set a static IP address for the container mail-postfix (see the commented line for ipv4_address in the example above) and use this IP address instead. But using the container name is probably more convenient.

Also in the wizard, if we have chosen to build on the image catatnight/postfix, we will need to enter the user name and password for the SMTP server (which we have set previously in the docker-compose file).

Ii we have built on the image tozd/postfix, we do not enter the password (authentication without a password is used there).

The plugin Post SMTP allows us to send a test e-mail. So, send an e-mail to the e-mail address check-auth@verifier.port25.com. You’ll get a response with the information if your e-mail has been set correctly and what could be improved to increase your mail deliverability. If everything is OK, the reply e-mail will contain the following lines:

Summary of reply from check-auth@verifier.port25.com
check-auth@verifier.port25.com

4. E-mail Related DNS/rDNS Records

There are several mail-related DNS records. Some of them increase mail deliverability and some don’t.

E.g. if you plan to work with your mail on-line, you would need a web mail client for it. You would need a subdomain where this mail client will be available. E.g. mail.example.com. And you will need an A record added for such a subdomain at your DNS server. This record will be mail-related. But it does not influence your mail deliverability in any way.

An MX record is also not necessary for sending mail. If you plan to accept e-mails (e.g. by having a POP3 or IMAP email server), you will need an MX record. But if you are only sending e-mails via SMTP (you do not accept e-mails at the server, just sending) you don’t need an MX record. An MX record does not influence the outgoing mail delivery.

But you certainly need a PTR record. Please see this post for details.

Please keep reading for more details on mail-related DNS/rDNS records.

MX Record

We’ll consider an MX record here only because it is mail-related. You do not need an MX record for sending mail. You need it for accepting mail only. So if you need only e-mail sending functionality, you don’t need to set this record. And can safely skip to the next section.

To check if your mail domain (or main domain if you do not have a special mail subdomain set) already has an MX record set for it run (on Linux or under Mac):

dig MX example.com +short

There are some special cases when you could have more than 1 MX record for 1 domain. But usually, you do not need it. So if you already have an MX record, normally there is no need to add another one. 2 mail providers will be unable to handle mail for 1 domain at the same time anyway (only 1 of them will do it at a time).

Adding an MX record is very straightforward. Normally you could find a help page for your DNS server describing how to do that. E.g. adding an MX repost at Namecheap DNS server is described here.

PTR record

A PTR record (other names: a pointer record, a Reverse DNS record, an rDNS record) is used for reverse DNS lookup. In mail functionality, this is the record by which a mail server (at the mail receiving side) finds your sender mail server domain name by its IP address. You need to add this record if you are sending e-mails and you want to increase mail deliverability.

Normally you don’t set a PTR on your DNS server. You set it at your IP address provider (usually your hosting company). Probably you’ll need to google on how to set the PTR record for your servers at your hosting company. E.g. how to set a PTR record at Hetzner is described here and here, for DigitalOcean it is described here, etc. Normally you can easily find how to set the PTR record at your hosting company servers by running a Google query like “<my hosting company name> set ptr record”.

You can check the PTR record you have set for your server currently (please substitute your server IP address in the commands below):

under Windows:

nslookup 116.203.140.161

under Linux and under Mac:

dig -x 116.203.140.161 +short

Or use an online reverse lookup tool like MXToolbox, Hacker Target,

To increase your mail deliverability set the PTR record for your sending mail server correctly (e.g. for this site it is set for wpdiaries.com since I did not need a separate mail-related subdomain here).

For more information on PTR records please see this article on namecheap.com.

SPF record

An SPF (Sender Policy Framework) record is necessary to decrease e-mail spoofing (a technique when spammers send SPAM e-mails using your domain e-mail in the FROM: field).

You can use a wizard like spfwizard.com to help you to form an SPF record.

E.g. for wpdiaries.com I checked if I had any current SPF record already:

dig TXT wpdiaries.com +short

And found out NameCheap (which provides registrar, DNS and e-mail forwarding services for my domains) had one generated for me already:

v=spf1 include:spf.efwd.registrar-servers.com ~all

So using spfwizard.com and taking into account that sometimes I reply via the Google SMTP server (and I did not want my e-mails to be rejected), for wpdiaries.com I have generated the following SPF record:

wpdiaries.com.      IN TXT     "v=spf1 a ptr include:spf.efwd.registrar-servers.com include:_spf.google.com ~all"

After forming an SPF record for your domain, you’ll need to add the record as a usual TXT record at your DNS server.

E.g. the SPF TXT record for wpdiaries.com on the Namecheap DNS server looks like this:

SPF TXT record on Namecheap DNS server
SPF TXT record on Namecheap DNS server

Then you can use one of SPF record validators (e.g. this one) to check if your SPF record exists and has correct format.

You could also check this article for information about SPF records. I think it is pretty useful.

DKIM record

The DKIM (DomainKeys Identified Mail) is a digital signature added to your e-mails. You need this record to increase mail deliverability.

We have considered adding a DKIM record for Postfix when discussed using the image catatnight/postfix right above.

You can find more information on the DKIM record in this, this, this and this tutorials.

Conclusion

I hope you’ve enjoyed the article.

Do you have any questions?

Or maybe you have any ideas on what I could add to the article?

Or maybe you could tell me which subjects you’d like to read about in future articles?

In this case, please do not hesitate to post a comment below.

I would be very glad to hear from you.

6 thoughts on “How to Provide SMTP Email Functionality for the Official Docker WordPress Image”

  1. Thanks for the post. Docker WordPress on a VM relaying through an Exchange mail server . Nice to see lots of options for solving email issues.

    Probably the most helpful piece for me was Post SMTP. The SMTP plugin I was using was probably fine, but Post SMTP displays error messages when your test mail fails to send, which helped me track down that I had forgotten to whitelist my WordPress VM in Exchange, so it was refusing to forward mail outbound.

    Solving the problem is 5% of the effort; troubleshooting is 95%.

    1. Thank you! Yes, I agree completely. For me, troubleshooting often also takes much more time than actually correcting the code. Thanks for your comment!

  2. Thanks so much for this. I’m new to Docker and am trying to move several WordPress sites into containers on one host. It’s going well, be I have noticed that WordPress is unable to send mail. This blog post is right on the mark, and you only published yesterday – how fortunate for me!

    Many thanks, it is appreciated.
    Mike.

    1. Thank you very much for your comment! I am really glad to hear the article has been helpful to you. I also have several sites (most in WordPress) on one host. I find it very convenient. E.g. it’s very easy to use different versions of PHP, MySQL, etc. on different sites when necessary. And all this on one host. Or e.g. I estimate it would take just about 30 minutes to relocate all these sites together to another hosting company. If this one ever has technical issues. 🙂

      Thanks for the kind words! And I wish you the best of luck on your project!

Leave a Reply

Your email address will not be published. Required fields are marked *