Discourse Docker container: send mail through Exim


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 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, otherwise it would reject incoming mails from that network. These are the relevant keys in /etc/exim4/update-exim4.conf.conf:


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('')
>>> server.set_debuglevel(1)
>>> server.sendmail("rofl@gehrcke.de", "jgehrcke@googlemail.com", "test")
send: 'ehlo []\r\n'
reply: '250-localhost Hello [] []\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 [] []
SIZE 52428800
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='' 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. in my case.

Hope that helps!

One Pingback/Trackback

  • Pingback: Discourse on Debian Wheezy via Docker | Jan-Philip Gehrcke()

  • 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.

  • 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.

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

      • I did the RTFM job for you, …. ;-)

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


        The list of “trusted” remote SMTP clients.

        mynetworks =

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

        • 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` 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.

        • 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:

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

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

  • Luke

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