Self-hosting a Discourse server with Apache and Postfix

Discourse is a great discussion and community system, distributed under the GPL. They would prefer you to pay them to host it for you. But you can also host it yourself.

If you choose to self-host, they also prefer you to do it in a rather specific way that assumes a dedicated VPS on which you have no web or mail server running already. That wasn’t the case for me, so I did it my way.  Here’s my notes in case anyone else wants to know.

This guide makes the following assumptions (I’m running Ubuntu Server 22.04, but this should work on any similar Linux distro):

  1. That you are familiar with setting up Apache hosted websites secured with Letsencrypt (using certbot).
  2. That you are running Postfix and Dovecot (as per current Ubuntu defaults for mail) and are familiar with setting up mail accounts for these.
  3. That you have a firewall, but know nothing about Docker.
  4. You don’t want to have more than one Discourse server. You can, (may be easy to do with multiple containers at least) but I haven’t done it here.

Know also that I wanted to install Discourse to replace Mailman (which has effectively died a death on Ubuntu). So these instructions also tell you how to set up Discourse (almost!) as a classic email discussion list.

Warning: Due to some technical architectural stuff, you may not want to use Apache on a high-volume setup. Apache doesn’t play as well with Discord under high loads – nginx is better if you can use it, but I can’t.

Note: I’ve not used any screenshots because they’ll go out of date and confuse things, sorry.


Install docker

You are advised not to install Docker from the Ubuntu repos and instead use their PPA here. Once installed it should give you a new network interface called “docker0”. You need to add that interface to your inbound firewall rules to allow all traffic in to it (it’s a non-routable address so don’t worry):

iptables -A INPUT -i docker0 -p tcp -j ACCEPT

Configure Apache

From here I assume your server name is “discourse.mydomain.com”.

Create a vanilla web host for your Discourse server on port 80. It doesn’t have to have a docroot, just as long as it’s running.

Set up the SSL version with cerbot as you would normally. Certbot will add a redirect from port 80 to 443 as part of that.

Now in your port 443 config, add the following lines inside the VirtualHost stanza, and reload Apache:

SSLProxyEngine on 
RewriteEngine on
ProxyPreserveHost On
ProxyRequests Off
RequestHeader set X-Forwarded-Proto expr=%{REQUEST_SCHEME}
RequestHeader set X-Real-IP expr=%{REMOTE_ADDR}

ProxyPass / unix:/var/discourse/shared/standalone/nginx.http.sock|http://localhost/
ProxyPassReverse / unix:/var/discourse/shared/standalone/nginx.http.sock|http://localhost/

It won’t do anything just yet. So leave Apache be for now.

Configure Postfix/Dovecot

Add the Docker network to your Postfix $mynetworks (in main.cf). In my case that was “172.17.0.0/16”. Reload postfix.

Now create an SMTP AUTH mail account with the details you supplied in app.yml. That account doesn’t need to receive mail, only send out via Postfix.

As an aside: I don’t know why you have to set up an SMTP AUTH account just to send email from the host. But hey!

Install the Discourse container

As per their instructions (link might go out of date), clone the Official Discourse Docker Image into /var/discourse like this:

sudo -s
git clone https://github.com/discourse/discourse_docker.git /var/discourse
cd /var/discourse
chmod 700 containers

You will need to be root through the rest of the setup.

Copy the file “samples/standalone.yml” to “containers/app.yml”

Now edit the config file in “/var/discourse/containers/app.yml”.

Note: “app” is the name of the Discourse container, so remember that as you may need to use it in Docker commands perhaps.

First, add the following line in the “templates” section:

 - "templates/web.socketed.template.yml"

Then comment out all the lines in the “expose” section, so it looks like this:

# expose:
# - "80:80" # http
# - "443:443" # https

Set the “DISCOURSE_HOSTNAME” variable, and  “DISCOURSE_DEVELOPER_EMAILS” variable to your chosen admin email (the one you need to pick up the initial site verification from).

This is the main bit for email (it’s possible you may have different ports and stuff, but see how you go as this is pretty standard I think):

DISCOURSE_SMTP_ADDRESS: your.mailserver.com
DISCOURSE_SMTP_PORT: 587
DISCOURSE_SMTP_USER_NAME: discourse
DISCOURSE_SMTP_PASSWORD: "passwordHere"
DISCOURSE_SMTP_ENABLE_START_TLS: true 
DISCOURSE_SMTP_OPENSSL_VERIFY_MODE: none
DISCOURSE_SMTP_DOMAIN: mydomain.com
DISCOURSE_NOTIFICATION_EMAIL: noreply@mydomain.com

Tip: you may want to check that the TLS certificate being used is the correct one for the hostname you give. Check that with this command to make sure the name returned is the same one as you’re supplying in the Discourse config:

openssl s_client -connect your.mailserver.com:567 -starttls smtp -showcerts 2>&1|grep "depth=0"

Optional. I have a “DISCOURSE_MAXMIND_LICENSE_KEY:” but you might not.

Now build Discourse with:

/var/discourse/launcher rebuild app

And wait a veeery looong time.

Note: I suddenly got a low space warning when I was fiddling with all this. It seems Docker can use up your disk in some circumstances. I freed up about 20Gb with this command, and it’s been OK since:

/usr/bin/docker system prune --all --volumes --force

Configure Discourse

General stuff

Once Discourse has built (it looks like it’s spewing errors, even when it’s done), go to your new web URL for the server and with luck you can register it all OK. If you don’t get the setup email, try running /var/discourse/discourse-doctor.

Log in as admin. As you are running over https, they say you need to check the “force https” option in the Security section of the Settings, so you may as well (is there anyone running a website without https these days?).

Email replies

There are two main functions here: one is having the option to reply to Discourse’s email notifications using email (and not the web UI). The other is to start new conversations with email and reply to comments on those. So like a traditional email discussion list.

Note: You can also do a read-only mailing list, but I don’t cover that here.

First, get email replies to work. For this you need to get Discourse to poll a POP3 account to which the replies are sent using email “plus notation” (used to identify which discussions they belong to).

So, set up another email user on your host to receive replies and use a POP3 account. For example, discourse-replies@yourdomain.com.

Now in the Discourse admin section, check the “reply by email enabled” and set the required email address and format as “discourse-replies+%{reply_key}@yourdomain.com”

Set “pop3 polling enabled” and fill in required fields for that.

Note: Discourse also has another method for doing all this, but I don’t understand how that can work in my situation.

Don’t forget you may want to have an auto-responder on the noreply email you have in Discourse’s app.yml. Emails by default get sent from that address.

Discussions by email

Along with being able to reply to posts by email, you can also replicate a traditional email discussion list in the sense of being able to send an email to Discourse and then have that go out to all (or a sub-set) of users on your Discourse server.

To allow people to start new discussions to do this, check “email in” for creating new threads by email.

Here’s where it gets a bit funky though…

You now need to set up email addresses on your mail server that have the same name as your Discourse “categories” or “groups”, so that people can send to these and Discourse can know what’s going on.

So if you want to be able to create a new discussion in the “General” category, you need to set up “general@yourdmain.com” and have that deliver to the same mailbox as “discourse-replies@yourdomain.com”. That will then create the first post, but it won’t email anyone until somebody replies using in the web UI. At that point, an email discussion can then ensue, but only among those replying to that thread (Discourse calls them “topics”).

If you (also) want to create a new discussion that sends out an email to all (or a sub-set) of users, then you need to create an email that has the same name as a Discourse group, and have that deliver to the replies mailbox too. Obviously, you need to have the relevant people in that group, and there are several ways of doing that (you can opt to have everyone who joins put into a group automatically, for example).

In both cases, you need to tell your users what those email addresses are. Spam is a worry, but Discourse tries to make sure only members who have accounts can email in.

At that point, you should have Discourse working as a mailing list and web discussion forum.