[Docker] How to Set up Postfix Containers and Send Emails From WordPress in Docker

Create Docker Postfix Containers and Add SMTP to WordPress

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. For example, 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.

Contents

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

1.1 How to Set up a Postfix Docker 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:alpine-38
    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 (where the prefix mail_ is the <current directory name>_)

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 – IP Address Management – uses the default driver here). 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:alpine-38
    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 (where the prefix mail_ is the <current directory name>_)
    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 Set up a Postfix Docker 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 (where the prefix mail_ is the <current directory name>_)
    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:6.6.0-php8.3-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).

If we’ve 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.

For example, if you plan to work with your mail online, you would need a web mail client for it. You would need a subdomain where this mail client will be available. For example, 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 (for example, 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.

For example, for wpdiaries.com I checked if I had an 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.

For example, 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 the SPF record validators (for example, this one) to check if your SPF record exists and has the 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 we talked about 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.

If you do not need to send mail from WordPress in Docker, but all you need is a simple solution for forwarding your domain mail, please check this 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.

Sergei Korolev
Sergei Korolev
Web developer and the author of all articles on this site. With over 25 years of programming experience, he has specialized in web programming for more than 19 years. He is a Zend Certified Engineer in PHP 5.3. He is available for hire at a rate of $60 USD per hour. You can see his resume here. Please contact him via this contact form to get a quote. He currently lives in Belgrade, Serbia.

26 thoughts on “[Docker] How to Set up Postfix Containers and Send Emails From WordPress in Docker”

  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!

  3. This is hands down the most informative post I found (I was looking for hours and even days) pulling my hair out trying to get mail sent through my traefik-nginx-wordpress-redis docker containers on a Digital Ocean VPS. I am configuring tis right now and this is an amazing start and hopefully final fix for this perplexing issue and just published right when i started looking into this issue. I hope this post stays since it is killer instructions.

  4. Hello, thank you for your article.
    Just a simple question, if I (like you) have several wordpress containers on the same host, do I have a single catatnight/postfix container for all wordpress containers or are there as many catatnight/postfix containers as there are wordpress containers?
    Currently I use juanluisbaptiste/postfix for 1 wordress container, so for 5 wordpress I have 5 times the postfix, is there a solution with only 1 smtp/postfix container for all wordpress containers?
    Thank you

    1. Hello.

      Thanks for your comment.

      I have one (the same) catatnight/postfix container for all my sites currently. I just added all my WordPress containers to the network mail where this only catatnight/postfix container is running (each my WordPress site is actually added to several networks – not just mail – and all these networks are responsible for different things) – please see Section 2. My sites are located on the same server in different docker containers. But if you use DKIM verification with this single catatnight/postfix container, it will be valid for 1 your domain only. You will be able to send mail from all your sites. But the DKIM record will be valid for 1 of your sites (domains) only.

      So, if your sites are located on different domains and if you want to have a valid DKIM verification for all of these domains, you could need several catatnight/postfix containers running. And configure each of them for its own maildomain. And with its own DKIM verification keys.

      Also, having a separate catatnight/postfix with its corresponding separate network for each WordPress container could be possibly better for security considerations. I think it could be better not to have a single one network where each of your WordPress containers will have an IP address assigned to it. Maybe I am a little bit paranoid about such things. But I think in the future I will have different mail-related networks for different WordPress projects.

    1. Thanks for the good words! I really appreciate them.

      I’m very glad to hear you’ve found the article useful. Thanks.

  5. In docker-compose yml file, the tag is missing :

    image: tozd/postfix:

    List of tags :
    tozd/postfix:ubuntu-xenial
    tozd/postfix:ubuntu-bionic
    tozd/postfix:ubuntu-trusty
    tozd/postfix:alpine-38

    There is no “latest” tag. so the default value isn’t working.

    1. Thanks for telling me!

      Previously tozd/postfix used to have the tag latest. My mail server in docker has been working based on it for about 1 year. I had to explicitly remove the docker image for tozd/postfix to reproduce the error when testing now. I’ve replaced it with tozd/postfix:alpine-38 in the examples in the article.

  6. Thank you for the article! I have followed all the instructions and it seems to work. With the mentioned plugin I get the message “Your message was delivered (19 ms) to the SMTP server!”. Here is my question: This means, that my wordpress container has successfully communicated with my postfix container. But how to configure the postfix container that this message should actually be send out to the recipient of the test email? Using wireshark I do not get any traffic on port 25 during the test email. Does the post fix container need to be configured on top of this tutorial? Container port to host port mapping? Or am I missing something here? Thanks.

    1. Thanks for the good words!

      At the time of writing this article, I’ve been checking the ways to use Postfix with WordPress described in this article. So, at that time, I tested the configurations described in the article. Currently, this very server uses the the catatnight/postfix image for a Postfix container as described in section 1.2 of this article. No, I did not do any additional port mapping.

      If the e-mail sending functionality did not work for me, I would have checked the mail logs inside the Postfix container first. For catatnight/postfix, you could look at this thread for the information on how to do that. Normally it gives an idea of what is going on…

  7. Hi
    first of all, I want to appreciate this helpful article.
    I’ve implemented postfix by catatnight/postfix image in separate container with dkim. but I cannot connect to this container. I got “Unable to connect to remote host: Connection refused” error. I can give more information about my .yml and config files.
    thanks a lot

    1. Hi! Thanks for the nice words!

      This is strange. I keep using catatnight/postfix and it works fine for me. E.g. I use it on this site. This very container sent an e-mail notification to me about your comment.

      Have you added the mail container network to your WordPress container as external? And are you connecting to the mail server from WordPress using the mail container name?

      1. Thank you for your reply.
        My questions are:
        1) you have a real domain name, not a dev domain like dev.example Would a local domain like this still work?
        2) would the network config go under the php service?

        1. 1) I am using this container configuration on wpdiaries.com. Yes, it is a real domain. This is how I got a notification about your question to my e-mail just a few minutes ago.

          For a local domain, I did not try it with this particular configuration, sorry. Most probably you would be able to send e-mails. But they may be rejected by gmail or other mail services…

          Some time ago I sent a few testing e-mails from a VirtualBox virtual machine to my gmail e-mail address. And I was able to receive them despite the domain was not real… Still, that could change… They could always make rules more strict…

          2) Yes, if your php service is going to communicate with the Postfix container, they must be in the same Docker network (the Postfix container we are talking about does not publish any ports for outside use). So you could give your php service access to the network, where Postfix is running, in basically the same way, as it was done for the WordPress container configuration here.

          Thanks for your questions.

          1. the nginx container fails to start only when the external mail container is running

          2. Maybe it is worth running

            docker ps -a

            to find the container ID. And then

            docker logs first-3-letters-of-the-container-ID

            to see the messages output by the nginx container during the initialization. It could show the reason why the container did not start.

Leave a Reply

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