Here’s how I deployed and self-host Discourse on DigitalOcean like a grown-up: no gimmicks, just a clean, reproducible setup you can live with for years.
You’ll need a DigitalOcean account (obvious, but say it out loud), a domain you control, and an email/SMPP—no, not that—SMTP provider (Mailgun, Postmark, SendGrid, SES… anything reliable). Discourse is a modern forum built by folks who actually use it: Ruby on Rails backend, Postgres + Redis, and a battle-tested Docker deploy that’s been their preferred path for ages. We’ll lean on that, because the “official way” keeps you out of weird snowflake configs later.
What you’ll set up
- A fresh Ubuntu LTS Droplet (2 GB RAM is workable; 1 GB is asking for pain unless you add swap; 2 GB RAM plus swap is still recommended for smooth rebuilds/upgrades).
- DNS pointing
forum.yourdomain.com
→ Droplet IP. - Discourse via the official
discourse_docker
repo (./discourse-setup
handles most of the heavy lifting and Let’s Encrypt for you). - Working outbound email (Discourse won’t finish bootstrapping without it).
- A backup routine and a one-liner upgrade path so you don’t have to babysit it.
Create the Droplet
Stick with Ubuntu LTS (22.04 or 24.04 are fine). Size? For a new/small community: 2 GB RAM / 1 vCPU / 50+ GB SSD. If you must start smaller, make swap (below) or you’ll hit a wall during rebuilds and web-upgrades.
Harden the box right away (SSH key login, firewall on). On first SSH in:
sudo apt update && sudo apt -y upgrade
sudo apt -y install ufw git
sudo ufw allow OpenSSH
sudo ufw allow http
sudo ufw allow https
sudo ufw --force enable
Point DNS
Create an A record for forum.yourdomain.com
to your Droplet’s IPv4. Do this before install so Let’s Encrypt can validate during setup.
(If RAM < 4 GB) Add swap
If you’re on 1–2 GB RAM, add 2 GB swap. Discourse’s own docs have recommended a swapfile for years, and in 2025 they explicitly warn that 2 GB RAM needs at least 2 GB swap for upgrades to succeed. Do it once, do it right.
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
Verify:
free -h
Install Discourse (official method)
The Discourse team ships and supports the Docker-based installer. Don’t freestyle this with random compose files; you’ll make upgrades miserable later.
sudo -s
cd /var
git clone https://github.com/discourse/discourse_docker.git discourse
cd /var/discourse
./discourse-setup
The setup script will ask for:
- Hostname:
forum.yourdomain.com
- Admin email: where your first admin invite goes
- SMTP server details: address, port, username, password, and
smtp_from_address
It will also offer to set up Let’s Encrypt automatically (say yes, supply a working email). If your SMTP is wrong or blocked, you won’t receive the admin link—so test credentials with your provider first. Mailgun/Postmark docs and Discourse’s troubleshooting threads exist for a reason.
First boot can take several minutes. When it’s done, visit https://forum.yourdomain.com
, request the admin link (or use the invite link sent), and create your admin account.
Post-install sanity checks
From the admin UI (/admin
), set:
- Email → test a message.
- Backups → enable automatic backups to local storage for now (we’ll cover CLI + off-box later).
- Basic settings (title, logo, colors, auth).
If you didn’t get email, fix SMTP first—Discourse lives and dies by outbound email.
Routine maintenance (the boring part you’ll thank yourself for)
When you see updates available, SSH in and run a rebuild. This is the boring, dependable way to keep current:
cd /var/discourse
sudo ./launcher rebuild app
Rebuild pulls the latest images, applies migrations, and restarts cleanly. (There’s also a web upgrader; with 2 GB RAM it can be tight—CLI rebuild remains the safest bet.)
Backups (CLI + off-server)
Nightly automatic backups to local storage are fine, but copy them off the server regularly. Discourse ships a clean CLI:
cd /var/discourse
sudo ./launcher enter app
discourse backup
exit
Then fetch the backup and your container config:
scp root@YOUR_SERVER_IP:/var/discourse/shared/standalone/backups/default/LATEST_FILENAME.tar.gz .
scp root@YOUR_SERVER_IP:/var/discourse/containers/app.yml .
To restore on a fresh box, upload the backup in /admin
or use the CLI restore flow documented by Discourse.
Logfiles & quick triage
If things go sideways after a rebuild:
cd /var/discourse
sudo ./launcher logs app
Look for SMTP errors, Let’s Encrypt renewal errors, or plugin failures. Most “it suddenly broke” moments after an upgrade are a bad plugin or low memory.
Email that actually delivers
Set proper SPF, DKIM, and DMARC for your domain with your SMTP provider. In Discourse app.yml
(if you ever need to tweak by hand), the relevant bits live under params:
→ env:
(the discourse-setup
wizard writes these for you). If you switch SMTP providers later, update those values and rebuild.
After any change:
cd /var/discourse
sudo ./launcher rebuild app
Plugins (be conservative)
Discourse runs fast out of the box. When you add plugins, add one at a time, and only well-maintained ones. You’ll edit /var/discourse/containers/app.yml
, add the plugin under hooks:
→ after_code:
→ exec:
→ cmd:
(git clone). Then:
sudo ./launcher rebuild app
If a plugin causes issues, remove its line, rebuild, and you’re back to stock.
SSL renewals, firewalls, and automatic updates
Let’s Encrypt renewals are handled by the container, assuming your DNS still points correctly. Keep UFW on, ports 22/80/443 only. Consider unattended-upgrades
for the host OS:
sudo apt -y install unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades
Common gotchas you can avoid
- No email on first run → your SMTP is wrong or blocked. Check provider ports and credentials; send a test from
/admin
and check container logs. - OOM during upgrades → add swap (2 GB), then
./launcher rebuild app
. The team explicitly warns about memory pressure in 2025. - One-click images → convenient but not the path Discourse supports when you need help. The official Docker method is the line of least regret.
- Compose-only installs → alluring, until upgrades bite you. Do it the official way first; experiment later if you must.
Quick start recap (copy/paste)
If you’ve already pointed DNS and have SMTP ready, the bare-minimum sequence looks like this:
# SSH in as a sudo user
sudo apt update && sudo apt -y upgrade
sudo apt -y install ufw git
sudo ufw allow OpenSSH && sudo ufw allow http && sudo ufw allow https
sudo ufw --force enable
# (Optional but wise if RAM < 4 GB)
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile && sudo mkswap /swapfile && sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
# Install Discourse the supported way
sudo -s
cd /var
git clone https://github.com/discourse/discourse_docker.git discourse
cd /var/discourse
./discourse-setup
# Later, to upgrade safely
cd /var/discourse
sudo ./launcher rebuild app
That’s it. Keep it simple, keep it supported, and you’ll have a solid Discourse you won’t dread maintaining.
If you want, I can tailor this to your exact stack (domain, SMTP provider, Droplet size) and pre-fill an app.yml
so you can be up in minutes. Let me know in the comments!
Does this install Ruby and everything else automatically also?
Yes — and that’s the beauty of doing it the “official” way with the discourse_docker
repo. You don’t have to go out and install Ruby, Rails, PostgreSQL, Redis, or any of the other moving parts yourself.
Here’s what actually happens under the hood:
- The installer (
./discourse-setup
) pulls a prebuilt Docker image maintained by the Discourse team. - That image already has Ruby, Rails, Postgres, Redis, NGINX/Passenger, ImageMagick, and all the OS-level dependencies baked in.
- Your
app.yml
(created during setup) tells the launcher how to wire it all together — hostname, SMTP credentials, Let’s Encrypt, plugins, etc. - When you run
./launcher rebuild app
, it recreates the container environment from scratch with all those dependencies intact and upgraded.
So in practice, the only things you install directly on your Ubuntu Droplet are Docker, git, and the discourse_docker
scripts. Everything else lives inside the container. That’s why upgrades are so clean — you’re just pulling a fresh version of the official image, not fiddling with system packages.
If you were to try doing a “bare metal” Discourse install (no Docker), you’d be manually compiling Ruby, tuning Postgres, setting up Redis, NGINX, Passenger… and you’d be stuck repeating that dance every upgrade. The Docker way avoids all of that.