Table of Contents

Discourse

Discourse is a modern, open-source discussion platform built for the next decade of the Internet. It functions as a mailing list, discussion forum, and long-form chat room, designed from the ground up to address the challenges of community management and online discussion.

Overview

Discourse is written in Ruby on Rails with an Ember.js frontend. It runs inside Docker containers, which simplifies deployment but creates opacity when deviating from the standard configuration. The platform emphasizes civilized discussion through features like trust levels, flagging systems, and moderation tools.

Key Characteristics:

System Requirements

Component Minimum Recommended
RAM 1 GB 2+ GB
CPU 1 core 2+ cores
Storage 10 GB 20+ GB
OS Ubuntu 20.04+ Ubuntu 24.04 LTS

Architecture

Discourse ships as a monolithic Docker container containing:

The container exposes ports for HTTP/HTTPS traffic and handles TLS termination internally by default. However, production deployments frequently place Discourse behind a reverse proxy for flexibility in certificate management, load balancing, or multi-application hosting.

Installation

This section documents installation on a fresh Ubuntu server with backup restoration and nginx reverse proxy configuration.

Prerequisites

Initial System Setup

sudo -s
apt-get update && apt-get install -y git

Clone Discourse Docker Repository

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

Configure for Reverse Proxy

Before running the setup script, create the container configuration. This step is critical—configuring after initial build requires a full rebuild.

Create /var/discourse/containers/app.yml with reverse proxy settings:

Comment out SSL templates:

templates:
  - "templates/postgres.template.yml"
  - "templates/redis.template.yml"
  - "templates/web.template.yml"
  - "templates/web.ratelimited.template.yml"
  ## SSL templates - COMMENTED OUT for reverse proxy setup
  # - "templates/web.ssl.template.yml"
  # - "templates/web.letsencrypt.ssl.template.yml"

Modify port exposure:

expose:
  - "8080:80"   # expose container port 80 on host port 8080
  # - "443:443" # https - commented out

<WRAP important> The 8080:80 mapping is not well-documented. The reverse proxy (nginx) handles TLS termination and forwards plaintext HTTP to port 8080. </WRAP>

Run Discourse Setup

./discourse-setup --skip-connection-test

The setup wizard prompts for configuration values:

Parameter Example Value Notes
Hostname forum.example.com Must match your DNS record
Developer Email admin@example.com Receives admin notifications
SMTP Server smtp.provider.com Your mail provider
SMTP Port 587 TLS submission port
SMTP Username smtp-user Provider credentials
SMTP Password smtp-pass Provider credentials
Notification Email noreply@example.com From address for emails
Let's Encrypt Email OFF Critical: Enter OFF for reverse proxy

<WRAP tip> The build process takes 5-15 minutes depending on hardware. </WRAP>

Restore from Backup

If restoring from an existing Discourse instance:

Transfer backup file to server:

scp -P 22 discourse-backup.tar.gz user@server:/var/discourse/shared/standalone/backups/default/

<WRAP info> Adjust the port (-P), username, and hostname to match your environment. Do NOT change the name of the file, as it is metadata necessary for the restore. </WRAP>

Restore via Admin Panel:

  1. Access Discourse at http://server-ip:8080 (or domain if DNS configured)
  2. Navigate to Admin → Backups
  3. Locate the uploaded backup file
  4. Click Restore and confirm

The restoration restarts Discourse and may take several minutes depending on backup size.

Install and Configure Nginx

apt-get install -y nginx

Create site configuration at /etc/nginx/sites-available/forum.example.com:

server {
    listen 80;
    server_name forum.example.com;
 
    location / {
        proxy_pass http://localhost:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host $host;
 
        # WebSocket support (required for Discourse)
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
 
        # Timeouts
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
 
        # Buffering
        proxy_buffering off;
        proxy_redirect off;
    }
 
    client_max_body_size 10m;
}

Required headers for Discourse:

Enable the configuration:

rm /etc/nginx/sites-enabled/default
ln -s /etc/nginx/sites-available/forum.example.com /etc/nginx/sites-enabled/
nginx -t
systemctl reload nginx

Configure TLS with Certbot

snap install --classic certbot
certbot

Certbot automatically:

Reload nginx after certificate installation:

systemctl reload nginx

Configuration

Environment Variables

Key environment variables in app.yml:

Variable Purpose
DISCOURSE_HOSTNAME Primary domain name
DISCOURSE_DEVELOPER_EMAILS Admin notification recipients
DISCOURSE_SMTP_ADDRESS Mail server hostname
DISCOURSE_SMTP_PORT Mail server port
DISCOURSE_SMTP_USER_NAME SMTP authentication user
DISCOURSE_SMTP_PASSWORD SMTP authentication password
DISCOURSE_DB_SHARED_BUFFERS PostgreSQL memory allocation

Rebuild Container

After modifying app.yml:

cd /var/discourse
./launcher rebuild app

Maintenance

Common Commands

# Enter container shell
./launcher enter app
 
# View logs
./launcher logs app
 
# Stop/start container
./launcher stop app
./launcher start app
 
# Full rebuild
./launcher rebuild app

Backup Configuration

Backups can be configured in Admin → Backups → Settings:

Troubleshooting

Nginx Shows Default Page

SSL Certificate Errors

Backup Not Visible

SCP Syntax

API

<WRAP center round important 60%> AUTO-GENERATED DOCUMENTATION

This section was automatically generated and is subject to change upon human review. Last updated: 2026-01-19 </WRAP>

Channel Operations

Authentication Headers

All API requests require the following headers:

Api-Key: {your_api_key}
Api-Username: {username}
Content-Type: application/json

Webhook Event Types

Available webhook triggers include:

Avatar URL Extraction

Avatar URLs are provided in the avatar_template field of user objects in API responses.

Response Structure

{
  "user": {
    "id": 2,
    "username": "Chelsea",
    "name": "Chelsea Lee ᶘ ᵒᴥᵒᶅ",
    "avatar_template": "/user_avatar/meant2be.me/chelsea/{size}/3_2.png"
  }
}

Building Avatar URLs

Pattern:

{base_url}{avatar_template with {size} replaced}

Example:

https://baseurl.tld/user_avatar/baseurl.tld/username/48/3_2.png

Supported sizes: 20, 25, 32, 45, 48, 60, 90, 120, 135, 240, 360, 480

Recommended for Discord: 128 or 256

Python Implementation

def get_avatar_url(user_obj, size=48):
    """Extract and construct full avatar URL from Discourse user object"""
    template = user_obj['avatar_template']
    avatar_path = template.replace('{size}', str(size))
    return f"https://meant2be.me{avatar_path}"
 
# Usage:
avatar_url = get_avatar_url(message['user'], size=128)

Bridge Architecture

Data Flow

Discourse → Discord (via webhooks):

  1. Webhook events trigger on chat message actions
  2. Webhook receiver endpoints process incoming events
  3. Forward to Discord via webhook or bot API

Discord → Discourse (via API):

  1. Discord bot listens for message events
  2. POST to /chat/api/channels/{channel_id}/messages
  3. Payload: {“message”: “text content”}

Implementation Considerations

API Response Examples

Chat Messages Response

{
  "messages": [
    {
      "id": 77045,
      "message": ":eyes:",
      "created_at": "2025-12-23T00:30:23Z",
      "chat_channel_id": 1,
      "user": {
        "id": 2,
        "username": "Chelsea",
        "name": "Chelsea",
        "avatar_template": "/user_avatar/baseurl.tld/chelsea/{size}/3_2.png",
        "moderator": true,
        "admin": true,
        "staff": true
      }
    }
  ],
  "meta": {
    "can_load_more_future": false,
    "can_load_more_past": false
  }
}

References

See Also