colocalhost | Colocataires Blog

Self-Hostember #4: Discourse

Posted on 4 mins

Self-Hostember

ℹ️ This post is part of a series , and it assumes that you’ve followed the instructions from Self-Hostember #0 first.

What?

The title on the Discourse website says

Discourse is the place to build civilized communities

which is a laudable goal and a pretty big claim for a piece of software, but I appreciate their optimism.

You might have come across Discourse-powered forums without realizing – generally they have a distinctive look, lots of nice whitespace and colour-coded sections. A great example is the forums for Framework laptops .

Framework Forum screenshot

This is pretty stark contrast to the early 2000’s vibe of things like phpBB:

phpBB screenshot

Web forums still exist, but by the 2010’s a lot of their communities moved to centralized services like Reddit. phpBB isn’t as user friendly and suffered from security issues and mailing-list style forums were wracked by migrations to and then from eGroups Yahoo! Groups groups.io as communities were bought, sold and shutdown by former web giants.

Funding & License

Discourse was initially released in 2014, billed as WordPress for forums. It’s developed by a profitable company (although they did raise $20 million of VC money in 2021) and it’s completely open source. It’s GPLv2.

Let’s install it.

Installation

We’ll mostly follow the official installation guide . The guide strongly states that you need a working SMTP server to use – our domain is hosted with Migadu GmbH in Switzerland and I’ll create a dedicated user on the account to send notifications.

If you don’t have email at your domain yet we’ll be posting about that soon but also you might not be able to get a working Discourse install. Discourse also provide a list of providers they recommend .

Mailbox configuration

Then we’ll become root, and checkout Discourse with git into a dedicated directory:

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

Because we’re being fancy and using this server for more than one thing (and we have a reverse proxy installed) we can’t use the ./discourse-setup command, which is only suitable for the simplest installations.

We’ll configure the app.yml, based on the template in `samples/standalone.yml:

templates:
  - "templates/postgres.template.yml"
  - "templates/redis.template.yml"
  - "templates/web.template.yml"
  - "templates/web.ratelimited.template.yml"
expose:
  - "127.0.0.1:8990:80" # custom port number, only listening on localhost
params:
  db_default_text_search_config: "pg_catalog.english"
  db_shared_buffers: "256MB"
env:
  LC_ALL: en_US.UTF-8
  LANG: en_US.UTF-8
  LANGUAGE: en_US.UTF-8
  # DISCOURSE_DEFAULT_LOCALE: en
  UNICORN_WORKERS: 4
  DISCOURSE_HOSTNAME: 'discourse.easuan.ca'
  DISCOURSE_DEVELOPER_EMAILS: 'aaron@colocataires.dev'
  DISCOURSE_SMTP_ADDRESS: smtp.migadu.com
  DISCOURSE_SMTP_PORT: 587
  DISCOURSE_SMTP_USER_NAME: discourse@colocataires.dev
  DISCOURSE_SMTP_PASSWORD: "hunter2"
  DISCOURSE_NOTIFICATION_EMAIL: discourse@colocataires.dev
  #DISCOURSE_SMTP_AUTHENTICATION: plain # plain|login|cram_md5
volumes:
  - volume:
      host: /var/discourse/shared/standalone
      guest: /shared
  - volume:
      host: /var/discourse/shared/standalone/log/var-log
      guest: /var/log

Now we should add it to our Caddy configuration and reload. This has to be done before the next step.

discourse.easuan.ca {
    reverse_proxy 127.0.0.1:8990
}

We’ll tell it to rebuild itself using our config. This takes a long time (70 minutes for me!):

# systemctl reload caddy
# ./launcher rebuild app
x86_64 arch detected.
WARNING: This output is designed for human readability. For machine-readable output, please use --format.
Ensuring launcher is up to date
Launcher is up-to-date
2.0.20251003-1437: Pulling from discourse/base
Digest: sha256:0db7c9b4598e2a1806658391669d791fd779aa4301fdfbed6a7f322bf64e66ab
Status: Image is up to date for discourse/base:2.0.20251003-1437
docker.io/discourse/base:2.0.20251003-1437
/usr/local/lib/ruby/gems/3.3.0/gems/pups-1.3.0/lib/pups.rb
/usr/local/bin/pups --stdin
I, [2025-12-04T02:00:15.125166 #1]  INFO -- : Reading from stdin
...

Truly lots and lots of output. Lots. It’s going to start PostgreSQL. It’s going to git clone. It’s going to install node_modules with pnpm. Then it’s bundle’s turn. Patience is a virtue.

Eventually our patience will be rewarded. When you see

2025-12-04 02:59:36.134 UTC [60] LOG: database system is shut down

You’re nearly done. Shortly after it’ll spit out a git SHA and then return you to your prompt and leave the server running:

63fbb2d8e379305e47ee846aa8b8ed525dd3f433e174efa337967e964f1001f3

Celebratory balloons

I genuinely feel like I deserve these congratulations.

When you create your initial user, pay attention to the password requirements. Your password must be 15 characters long. I missed this and I didn’t get an error but nothing … happened when I clicked register. I have been spoiled by form validation.

We can make our new forum public or private, invite-only etc.

Choosing what kind of forum we’ll run

I’ve even earned my first badge, although I don’t appreciate being called basic:

First badge

It actually has an extremely slick onboarding and first use experience for admins. This is one of those benefits of Open Source which aims to turn a profit: you care about onboarding:

Onboarding help

And if we look in the bottom left you’ll see that Discourse includes a whole chat system.

Not Slack

I’m impressed! This really could be a one-stop solution for hosting a hobby community, without having to also have a Discord or a Slack etc.

I guess the next step is to invite some friends?

Users page

It’s a big piece of software, so you’re going to want to look at official documentation which is obviously delivered as a Discourse forum.

Thanks for reading this far, and follow along for future posts in #Self-hostember or follow us on Mastodon to keep up to date!