Discourse Docker container: send mail through Exim

Introduction

The Discourse deployment was greatly simplified by introducing Docker support (as I have written about before). Discourse heavily depends on e-mail, and its ability to send mail to arbitrary recipients is essential. While the recommended way is to use an external service like Mandrill, it is also possible to use a local MTA, such as Exim. However, when you set up the vanilla Discourse Docker container, it does not contain an pre-configured MTA, which is fine, since many have a well-configured MTA running on the host already. The question is how to use that MTA for letting Discourse send mail.

Usually, MTAs on smaller machines are configured to listen on localhost only, to not be exposed to the Internet and to not be mis-used for spam. localhost on the host itself, however, is different from localhost within a Docker container. The network within the container is a virtual one, and it is cleanly separated from the host. That is, when Discourse running in a container tries to reach an SMTP server on localhost, it cannot reach an MTA listening on localhost outside of the container. There is a straight-forward solution: Docker comes along with a network bridge. In fact, it provides a private network (in the 172.17.x.x range) that connects single containers with the host. This network can be used for establishing connectivity between a network application within a Docker container and the host.

Exim’s network configuration

Likewise, I have set up Exim4 on the Debian host for relaying mails that are incoming from localhost or from the local virtual Docker network. First I looked up the IP address of the docker bridge on the host, being 172.17.42.1 in my case (got that from /sbin/ifconfig). I then instructed Exim to treat this as local interface and listen on it. Also, Exim was explicitly told to relay mail incoming from the subnet 172.17.0.0/16, otherwise it would reject incoming mails from that network. These are the relevant keys in /etc/exim4/update-exim4.conf.conf:

dc_local_interfaces='127.0.0.1;172.17.42.1'
dc_relay_nets='172.17.0.0/16'

The config update is in place after calling update-exim4.conf and restarting Exim via service exim4 restart.

Testing SMTP access from within container

I tested if Exim’s SMTP server can be reached from within the container. I used the bare-bones SMTP implementation of Python’s smtplib for that. First of all, I SSHd into the container by calling launcher ssh app. I then called python. The following Python session demonstrates how I attempted to establish an SMTP connection right to the host via its IP address in Docker’s private network:

>>> import smtplib
>>> server = smtplib.SMTP('172.17.42.1')
>>> server.set_debuglevel(1)
>>> server.sendmail("rofl@gehrcke.de", "jgehrcke@googlemail.com", "test")
send: 'ehlo [172.17.0.54]\r\n'
reply: '250-localhost Hello [172.17.0.54] [172.17.0.54]\r\n'
reply: '250-SIZE 52428800\r\n'
reply: '250-8BITMIME\r\n'
reply: '250-PIPELINING\r\n'
reply: '250 HELP\r\n'
reply: retcode (250); Msg: localhost Hello [172.17.0.54] [172.17.0.54]
SIZE 52428800
8BITMIME
PIPELINING
HELP
send: 'mail FROM:<rofl@gehrcke.de> size=4\r\n'
reply: '250 OK\r\n'
reply: retcode (250); Msg: OK
send: 'rcpt TO:<jgehrcke@googlemail.com>\r\n'
reply: '250 Accepted\r\n'
reply: retcode (250); Msg: Accepted
send: 'data\r\n'
reply: '354 Enter message, ending with "." on a line by itself\r\n'
reply: retcode (354); Msg: Enter message, ending with "." on a line by itself
data: (354, 'Enter message, ending with "." on a line by itself')
send: 'test\r\n.\r\n'
reply: '250 OK id=1X9bpF-0000st-Od\r\n'
reply: retcode (250); Msg: OK id=1X9bpF-0000st-Od
data: (250, 'OK id=1X9bpF-0000st-Od')
{}

Indeed, the mail arrived at my Google Mail account. This test shows that the Exim4 server running on the host is reachable via SMTP from within the Discourse Docker instance. Until I got the configuration right, I observed essentially two different classes of errors:

  • socket.error: [Errno 111] Connection refused in case there is no proper network routing or connectivity established.
  • smtplib.SMTPRecipientsRefused: {'jgehrcke@googlemail.com': (550, 'relay not permitted')} in case the Exim4 SMTP server is reachable, but rejecting your mail (for this to solve I had to add the dc_relay_nets='172.17.0.0/16' to the config shown above).

Obviously, in order to make Discourse use that SMTP server, it needs to be configured with DISCOURSE_SMTP_ADDRESS being set to the IP address of the host in the Docker network, i.e. 172.17.42.1 in my case.

Hope that helps!

Leave a Reply to Luke Cancel reply

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

Human? Please fill this out: * Time limit is exhausted. Please reload CAPTCHA.

  1. Luke Avatar
    Luke

    Thanks for the post. I also needed dc_minimaldns=’true’ to be set.

  2. nemagee Avatar
    nemagee

    I appreciate this post — I had slightly different needs for a general outbound SMTP relay for a number of servers within a WAN, and wasn’t able to figure out that Postfix (instead of Exim) running in the container needed to allow 172.17.x.x addresses as valid senders. Of course! — makes total sense now, but only thanks to you.

    1. Robert Avatar

      How do I tell postfix to accept 172.17.x.x as valid senders?

      1. Jan-Philip Gehrcke Avatar

        I did the RTFM job for you, …. ;-)
        http://www.postfix.org/BASIC_CONFIGURATION_README.html

        By default, Postfix will forward mail from clients in authorized
        network blocks to any destination. Authorized networks are defined
        with the mynetworks configuration parameter.

        http://www.postfix.org/postconf.5.html#mynetworks

        The list of “trusted” remote SMTP clients.

        […]
        Examples:
        mynetworks = 127.0.0.0/8 168.100.189.0/28

        That is, adding 172.17.0.0/16 to your mynetworks parameter should yield the desired effect.

        1. Robert Avatar

          Thanks for the reply! I came to the same conclusion, but was not sure about it. I still get a `socket.error: [Errno 111] Connection refused`. `ping 172.17.42.1` works though.

          I can access discourse from my host maschine, but I cannot ping the internet (web.de) from the discourse docker ssh shell. So probably I still have a connectivity problem.

        2. Melroy van den Berg Avatar

          Yep thanks! Now can I send e-mails from my docker container using my host machine which runs the Postfix service.

          In my case, the docker compose file looks like:

          environment: 
          - MAIL_URL=smtp://mailserver                               
          - MAIL_FROM=my@domain.org
          extra_hosts:                                                                                    
            - "mailserver:192.168.1.20"
          

          Where 192.168.1.20 is the local IP address of my server. So docker can find it.

  3. andrewsomething Avatar

    Can’t believe no one else has commented on this. Extremely helpful stuff! I totally understand why they suggest using something like Mandrill, but this would make a good default for just testing things out.

  4. […] send mail through the Exim MTA running on the Debian host. I have described that in more detail in another blog […]