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.
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:
| 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 |
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.
This section documents installation on a fresh Ubuntu server with backup restoration and nginx reverse proxy configuration.
sudo -s apt-get update && apt-get install -y git
git clone https://github.com/discourse/discourse_docker.git /var/discourse cd /var/discourse chmod 700 containers
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>
./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>
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:
http://server-ip:8080 (or domain if DNS configured)The restoration restarts Discourse and may take several minutes depending on backup size.
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:
X-Forwarded-For and X-Forwarded-Host - Discourse uses these for client IP detectionUpgrade and Connection headersproxy_buffering off - Prevents response buffering issuesEnable 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
snap install --classic certbot certbot
Certbot automatically:
Reload nginx after certificate installation:
systemctl reload nginx
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 |
After modifying app.yml:
cd /var/discourse ./launcher rebuild app
# 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
Backups can be configured in Admin → Backups → Settings:
ls /etc/nginx/sites-enabled/ls -la /etc/nginx/sites-enabled/forum.example.comnginx -tsystemctl reload nginxsudo nginx -t/var/discourse/shared/standalone/backups/default/ls -la /var/discourse/shared/standalone/backups/default/.tar.gz-P (not lowercase -p)scp -P <port> <source> <user>@<host>:<destination><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>
GET /chat/api/channelsGET /chat/api/channels/{channel_id}POST /chat/api/channels/{channel_id}/messagesGET /chat/api/channels/{channel_id}/messagesAll API requests require the following headers:
Api-Key: {your_api_key}
Api-Username: {username}
Content-Type: application/json
Available webhook triggers include:
Avatar URLs are provided in the avatar_template field of user objects in API responses.
{
"user": {
"id": 2,
"username": "Chelsea",
"name": "Chelsea Lee ᶘ ᵒᴥᵒᶅ",
"avatar_template": "/user_avatar/meant2be.me/chelsea/{size}/3_2.png"
}
}
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
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)
Discourse → Discord (via webhooks):
Discord → Discourse (via API):
/chat/api/channels/{channel_id}/messages{“message”: “text content”}{
"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
}
}