Discourse hosting is not “pick a PHP panel and upload files.” Official production is a Docker stack you rebuild, or an official hosted plan that makes the stack someone else’s pager. The first-week admin work — wizard, categories, welcome topics — already lives in the beginner’s guide. This article does not walk that wizard again. It is the ops cookbook: RAM and swap, ports, app.yml, rebuild windows, SMTP versus Discourse ID, backups (Meta 14855), S3, CDN, fail2ban, and when the Free / Pro / Business grid is the whole decision.
If you wanted architecture of a XenForo VPS, that is a different product. If you wanted “should we even use Discourse,” start with the three-way matrix and the ops close. Here you already chose Discourse and you need the box — or the invoice that replaces the box.
Official sources: discourse/docs/INSTALL-cloud.md (2026), discourse.org/pricing, Meta’s automatic backups topic (14855), and the optional-features links the install guide actually names (CDN, S3, webhooks, offline page). If a knob is not on those pages, this article does not invent it.
Three things people call “hosting Discourse”
Treat these as different products that happen to run the same software.
| Shape | Who owns PostgreSQL, Redis, rebuilds | When it is the right shape |
|---|---|---|
| Official hosted (Free / Pro / Business / Enterprise) | Discourse | You want a community, not a Docker hobby |
| Official Docker on a 64-bit Linux VPS | You | You already live in SSH and you want the software cost to be a VPS |
| Unofficial image / panel button / “Discourse on Plesk” | Whoever left the building | Almost never. Meta support expects the standard install |
Hosted can move via a backup. Self-host can move via a backup. The install guide is explicit that data belongs to the customer. That is the exit door. It is not a reason to start on Bitnami “to try it.” Trying it is what the Free hosted plan and *.discourse.diy are for.
A fourth shape exists in conversation: “we will put Discourse behind the company nginx that already owns 80/443.” That is an advanced reverse-proxy project, not the official one-liner. The cloud guide assumes Discourse owns ports 80 and 443. If you cannot give it those ports, stop and read the install guide’s troubleshooting before you invent a compose file.
Official Docker: the numbers that are actually published
The cloud install guide’s published requirements:
| Minimum | Recommended | |
|---|---|---|
| RAM | 1 GB with swap | 2 GB+ |
| CPU | 1 core | 2+ cores |
| Disk | 10 GB | 20 GB+ |
| OS | 64-bit Linux | Ubuntu LTS |
The installer auto-tunes UNICORN_WORKERS and db_shared_buffers from the machine you actually have. Believe it. A 1 GB droplet without swap is how people spend the first evening on OOM kills instead of DNS. The installer will offer to create swap on small boxes. If you skipped that prompt, create it yourself (example from the guide):
fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile swap swap defaults 0 0' >> /etc/fstab
That is a minimum-box move, not a scaling strategy. If Latest is slow under ten concurrent staff members, the next step is more RAM, not a cleverer swappiness tweet.
Other documented expectations:
- Root SSH on a fresh Ubuntu/Debian server. The supported one-liner:
wget -qO- https://raw.githubusercontent.com/discourse/discourse_docker/main/install-discourse | sudo bash
- Ports 80 and 443 free on the host. Check with
lsof -i :80andlsof -i :443. If nginx or Apache is already bound there, stop and disable it, or do not use this install path. - A hostname. Either your own domain (DNS A record to the server IP; wait for propagation — the guide says up to 48 hours, usually faster) or a free
*.discourse.diysubdomain claimed through Discourse ID. - Let’s Encrypt certificates are provisioned automatically on the official path. The guide does not ask you for a CSR.
Build time after you confirm settings is about 5–10 minutes. Rebuilds later are the same window. Plan them.
Disk is the number people under-buy. 10 GB is the published minimum for a new site. Uploads, backups kept on the box, and Docker image layers during a rebuild all want headroom. 20 GB+ is the published recommendation. If you will keep five full backups locally and you have a media-heavy community, size the volume like an adult and push backups off-box (S3 section below).
Hostname, Discourse ID, and the 10-minute code
Two official hostname paths:
| Option | Best for | What you do |
|---|---|---|
Free mysite.discourse.diy |
Testing, learning, hobby pilots | Claim at id.discourse.com, generate a 6-digit code (valid 10 minutes), paste it into the installer |
| Your own domain | Production | DNS A record to the VPS. Wait until it resolves. Let’s Encrypt needs that resolution |
The installer verifies the *.discourse.diy code with Discourse ID and then creates the A record for you. Use the same Discourse ID account that claimed the subdomain. If you stall, generate a fresh code. Production communities should not stay on discourse.diy any longer than it takes to prove the stack.
Own-domain failures are almost always DNS. The guide lets you pass --skip-connection-test to bypass verification temporarily. That flag does not invent a certificate. If Let’s Encrypt cannot see the name on 80/443, you do not have HTTPS.
SMTP versus Discourse ID (an ops decision, not a wizard tour)
The 2026 installer lets you skip SMTP. When you skip it, the installer enables login via Discourse ID:
- Email-based login through id.discourse.com (no local SMTP)
- Social logins: Google, Facebook, Apple, GitHub
- Web push notifications still work (browsers and PWA)
What you do not have until you add SMTP later:
- Traditional confirmation mail from your domain
- Digests
- Mailing-list mode
- Reply-by-email
You add SMTP later via Admin → Email or by re-running setup. The install guide says so. Recommended provider notes live in INSTALL-email.md in the Discourse repo. This article will not pretend your host’s PHP mail is a Discourse transport.
Ops consequences, stated as operations:
- A pilot or an internal room that is happy with social login can skip SMTP for months.
- A support community that lives in the inbox cannot. Digests and reply-by-email are how those people come back. Put SMTP on the launch checklist, not the “someday” list.
- Hosted plans have monthly email caps (the live pricing grid is the source — the public marketing table changes). A mailing-list-shaped community will hit them. Read the grid. Do not assume “hosted means infinite mail.”
- Switching from Discourse ID to local SMTP later is documented. Switching identity provider for existing humans is a support week. Do not treat login as a setting you flip for fun.
If you configured SMTP and nothing arrives, the official next page is Meta’s “Troubleshooting email on a new Discourse install.” Fix bounce/skip logs before you invite the public.
app.yml and the rebuild ritual
Configuration that is not a site setting lives in /var/discourse/containers/app.yml. Edit it. Then rebuild:
cd /var/discourse
./launcher rebuild app
Rebuild destroys the old container, bootstraps a new one, and starts it. That is about 5–10 minutes of downtime on a healthy box. Meta documents an offline-page theme component if you care. For a small site, a short window at a published time is enough. For a site people treat as production Slack, announce the window.
launcher commands the install guide actually names:
| Command | Job |
|---|---|
start / stop / restart |
Container lifecycle |
destroy |
Stop and remove |
enter |
Shell inside the container |
logs |
Docker logs |
bootstrap |
Build from template |
rebuild |
Destroy, bootstrap, start |
cleanup |
Remove stopped containers older than 24 hours |
Debugging the guide actually names:
./launcher logs app
./launcher enter app
cat /var/discourse/shared/standalone/log/rails/production.log
Do not start editing app.yml because a blog from 2018 said to paste twenty env vars. Change one thing. Rebuild. Confirm. The installer already tuned workers and shared buffers. Fighting those values on a 2 GB box is how you get a slower site.
Plugins on self-host are an app.yml plus rebuild story. Hosted plans restrict which plugins you may run. That is not a support slight. It is the difference between a SaaS and a box you own.
Hosted: Free, Pro $100, Business $500
discourse.org/pricing in 2026 lists Free, Pro, Business, and Enterprise. Prices are USD and change anytime — re-check the live grid before you write a purchase order. What the public page has listed:
| Plan | Public list price | What the page emphasizes |
|---|---|---|
| Free | $0 | Get started; no server to manage |
| Pro | $100/month | Unlimited members and chat, unlimited public/private categories, unlimited tags, custom groups, custom domain, API and webhooks, 15+ plugins, 5 staff seats, email support |
| Business | $500/month | Everything on Pro plus advanced reporting, automation, events, gamification, SSO (OIDC / OAuth2), 20+ extra plugins, 15 staff seats, priority support, migration services |
| Enterprise | Custom | 99.9% uptime, 50+ plugins, unlimited staff, GDPR/CCPA hosting story, design/dev services |
Hosted FAQ facts the page actually states:
- You can switch plans. Upgrade in the admin dashboard. Downgrade by contacting them.
- You can move self-install → hosted or the other way using a backup. They will take care of a move onto their hosting.
- Data always belongs to you. Back up from the browser. Migrate to or from any cloud provider.
- Pro/Business trials can choose Global or Europe hosting regions.
- Educational (85%) and non-profit (50%) discounts exist on Pro/Business with documentation — contact after starting a trial. Do not invent eligibility.
- Going over limits does not immediately kill the site; they will talk to you. Still size the plan like an adult.
Free is how you learn the product without SSH. It is not how you run a company support org. Pro is the first plan that looks like a public community (custom domain, API/webhooks, plugin set). Business is the plan that unlocks the SSO / automation / events / gamification / Doc Categories-adjacent catalogue the marketing page groups together. Enterprise is a conversation with sales.
Staff seats are for admins and moderators. Five seats on Pro is plenty for a small staff and a hard stop for a university volunteer army. Count humans before you count plugins.
If you are on the fence between hosted Pro and a 2 GB VPS, write down who pages at 02:00 when Let’s Encrypt or PostgreSQL next moves. If the answer is “nobody,” buy hosted.
Backups (Meta 14855) — the part you do before you care
Official optional features include automatic backups. Turn them on before the site has strangers. Download one backup to somewhere that is not the VPS.
Meta’s “Configure automatic backups for Discourse” (topic 14855) is the self-host reference. Settings it actually names:
| Setting | What it does | Documented default / range |
|---|---|---|
backup_frequency |
Interval in days | Default 7 (weekly). 1 = daily. 0 = off. Maximum 30 |
backup_time_of_day |
When the job runs | Default 3:30 UTC |
backup_with_uploads |
Include uploads | Default on. Off = database only |
maximum_backups |
How many to keep | Default 5. Older are deleted |
remove_older_backups |
Age cap in days | Blank = disabled |
Local path on a standard standalone install: /var/discourse/shared/standalone/backups/default.
Daily is not the official default. Jeff Atwood’s position on that Meta topic, paraphrased as operations: weekly is a solid starting point; daily is a setting you choose if you prefer it. Discourse will not run hourly backups from this setting — people who asked were told to do more frequent copies externally.
Hosted retains whatever the plan says it retains. Read the plan. Do not assume Meta 14855’s local path exists on a box you cannot SSH.
A backup is not a backup until you have restored it once on a scratch site. Official hosted ↔ self-host moves use this file. The pricing FAQ is explicit that Discourse-to-Discourse via backup is the easy direction.
Before every rebuild that scares you, take a backup. Rebuilds are routine. They are also the moment people discover they never downloaded one.
S3: backups and uploads are not the same bucket
Meta 14855 is blunt: storing backups and regular uploads in the same bucket and the same folder is not supported and will not work. Give s3_backup_bucket a prefix (my-awesome-bucket/backups) and keep those objects private.
Two official ways to send backups to S3-compatible storage:
Admin panel. Create a bucket. Set s3_backup_bucket, s3_access_key_id, s3_secret_access_key, s3_region. Set backup_location to S3. After that, backups upload to S3 and are no longer kept locally (local disk is temp during backup/restore). Browse them under the admin Backups tab and download one offsite anyway.
app.yml env vars (example shape from the topic, Scaleway shown as an example — use your provider’s endpoint):
DISCOURSE_S3_REGION: nl-ams
DISCOURSE_S3_ENDPOINT: https://s3.nl-ams.scw.cloud
DISCOURSE_S3_ACCESS_KEY_ID: my_access_key
DISCOURSE_S3_SECRET_ACCESS_KEY: my_secret_access_key
DISCOURSE_S3_BACKUP_BUCKET: my_bucket/my_folder
DISCOURSE_BACKUP_LOCATION: s3
If you only want S3 for backups, the topic says you can omit DISCOURSE_USE_S3, DISCOURSE_S3_CDN_URL, and DISCOURSE_S3_BUCKET, and you do not need the after_assets_precompile step. Those belong to the uploads story (Meta’s “Configure an S3 compatible object storage provider for uploads”). Do not copy a full uploads app.yml because you wanted offsite backups.
AWS users often add a lifecycle rule to Glacier (or the equivalent cheaper tier on another vendor) so five weekly backups do not become a surprise invoice. Other providers have similar archive classes. Confirm minimum retention — Glacier-class storage often bills a minimum number of days even if you delete early.
IAM: community replies on 14855 note that instance roles work when s3 use iam profile is on and the region is set. Policy documents must list both the uploads bucket and the backups bucket if you use both. “Access Denied” after a pretty UI setup is usually the policy, not Discourse being mysterious.
Uploads-on-S3 is a separate project: different bucket (or prefix), CDN in front, rebuild if you set it in app.yml. Do it when local disk or guest image weight is the problem. Do not do it on day two because a blog said “real sites use S3.”
CDN, MaxMind, and the optional list
The install guide’s optional-features list is short on purpose. After the site is up, you may add:
- CDN — Meta “Enable a CDN for your Discourse” (14857). This is an ops project: origin, cache headers, not caching the authenticated app blindly. Hosted plans advertise a global CDN as a plan feature. Self-host: only add a CDN when you have measured that guests are fetching assets from far away, or when S3 uploads need a public front.
- MaxMind — free GeoLite2 license key in
app.ymlasDISCOURSE_MAXMIND_LICENSE_KEYfor IP geolocation features. Optional. - Multisite — Meta 14084. Not this article. One community per container until you have a reason.
- Webhooks — Meta 49045. Covered in the Discord / chat-integration piece, not here.
- Offline page during rebuild — Meta 45238. Worth it once you have members who treat a 7-minute 502 as a fire.
You do not need MaxMind, a CDN, or plugins to have a hosted-quality small site. You need backups, mail you understand, and a box that is not thrashing.
Host hardening the install guide actually names
Post-install, the cloud guide strongly recommends:
# Ubuntu/Debian
dpkg-reconfigure -plow unattended-upgrades
apt install fail2ban
That is the whole official host-hardening paragraph. Unattended upgrades keep the Ubuntu side current. Discourse itself upgrades via /admin/upgrade (web UI, recommended) or ./launcher rebuild app. Discourse will email you when new versions are available if mail works.
fail2ban is brute-force protection on the SSH door. It is not a Discourse plugin. It does not replace trust levels or the review queue.
You still need:
- SSH keys, not passwords, on the VPS.
- A firewall that is not “everything open because Docker.”
- Snapshots at the VPS provider in addition to Discourse backups if the provider makes them cheap. Provider snapshots are a disk picture. Discourse backups are an application picture. Keep both until you have restored both once.
Do not share the VPS with a WordPress that already owns 80/443 unless you are deliberately doing reverse proxy. The official guide assumes Discourse owns those ports. Sharing is how beginners spend a week on TLS and then blame Discourse.
Upgrades without folklore
Hosted. They upgrade the software. Your job is reading release notes for site-setting changes and theme-component breakage. You do not SSH.
Self-host, preferred: visit https://your-forum/admin/upgrade and click Upgrade when the UI offers it. This is the path the install guide calls recommended.
Self-host, CLI: cd /var/discourse && ./launcher rebuild app.
When to use which: use the web UI when it works. Use rebuild when the UI cannot (failed bootstrap, plugin pin, you changed app.yml). Rebuild is also how plugin additions land.
Stay current. The guide’s sentence is the policy: stay current for features and security. Pinning an image “because rebuilds are scary” is how you inherit last year’s advisory. If rebuilds are scary, you have not taken a backup and you have not practiced on staging. Fix those, then click Upgrade.
A staging clone is a second container or a second VPS restored from last night’s backup. Practice the rebuild there once. Production rebuilds then feel like a known window, not a ritual.
Moving hosted ↔ self-host
The pricing FAQ is the source, not a migration consultancy brochure:
- Discourse-to-Discourse via backup is the supported move.
- Moving onto official hosting: they will take care of it.
- Moving off official hosting: you download a backup and restore it on a standard Docker install.
- Your data belongs to you either way.
Restore onto a clean target. Do not restore over a half-configured site you have been clicking around in. Hostname, SMTP, and app.yml secrets on the target are a post-restore pass. Test login, a public topic, and a backup download on the new side before you flip DNS.
DNS flip is the cutover. Lower the TTL the day before. Keep the old side read-only or off once you flip, so people do not post into a ghost.
This is not a XenForo host move (that article already exists) and not an XF→Discourse import. Imports are a different project with permalinks and a staging droplet. Hosting moves assume you are already on Discourse.
A worked size ladder (without fake metrics)
Use this as a conversation with whoever pays the invoice, not as a benchmark.
| Situation | Shape that matches | Why |
|---|---|---|
| You have never SSHed | Hosted Free, then Pro if the community is real | Week one should not be lsof |
| Public community, custom domain, a few staff, no SSO parent | Hosted Pro or 2 GB Docker | Pro if you want to fire the pager; Docker if you already have one |
| Parent site must own login (OIDC/OAuth2) | Hosted Business, or self-host plus DiscourseConnect | Business lists SSO on the public grid; self-host can do DiscourseConnect without that invoice |
| Docs + events + gamification as first-class hosted plugins | Hosted Business | That is what the $500 row is selling |
| Compliance, 99.9%, unlimited staff | Enterprise conversation | Not a VPS with nicer monitoring |
| You insist on $6/month | You do not have a Discourse host | 1 GB without swap is the failure mode the guide exists to prevent |
| You already run containers for a living | Official Docker on 2–4 GB, backups to S3 | Do not “improve” the one-liner |
Community practice for import droplets (million-post class) often quotes something like 4 vCPU / 8 GB for the import host. That is an import box, not your steady-state 200-member support forum. Do not buy 8 GB because an import thread exists. Buy 8 GB when a restore test says 2 GB is swapping during Sidekiq storms.
What not to do
- Do not install Bitnami, a Plesk “Discourse” button, or a random compose file from 2019 and then ask Meta why it is unsupported.
- Do not run production on 1 GB without swap.
- Do not keep backups only on the same volume as the app.
- Do not put backups and uploads in one S3 prefix.
- Do not skip SMTP on a community you promised would mail people, and do not promise mailing-list mode on hosted without reading the email cap.
- Do not rebuild during your region’s peak hour without an offline page and a backup.
- Do not edit twenty
app.ymlkeys at once. - Do not treat Free hosted as “Pro without the credit card.” Staff seats, plugins, custom domain, and API access are why Pro exists.
- Do not share 80/443 with another app on the official install path.
- Do not invent XenForo-style PHP-FPM tuning and apply it to Unicorn. Different animal.
Ops checklist
Decide the shape
- Hosted (which plan, which region) or official Docker on Ubuntu LTS
- You know who gets paged, and it is not “whoever notices Twitter”
If Docker
- 2 GB+ RAM (swap on if you are on 1 GB only as a lab)
- 20 GB+ disk, 2+ cores if you can
- Ports 80/443 free (
lsof) - A record or a
discourse.diycode you can paste within 10 minutes - Unattended upgrades + fail2ban
- You have run
./launcher logs apponce so the path is not theoretical
Mail and identity
- SMTP tested under Admin → Email, or Discourse ID documented as the login path
- You know whether digests are in scope this quarter
Backups
-
backup_frequencyset (weekly default is fine; daily if you prefer) -
maximum_backupsunderstood - At least one backup downloaded off-box
- S3 (or equivalent) for backups if the VPS is not the vault
- You have restored onto a scratch site once
Change management
- Rebuild window announced; offline page if members are twitchy
- Staging restore exists before the first scary plugin
- CDN/S3-uploads only after a reason, not as décor
Takeaways
- Official production Discourse is Docker on 64-bit Linux (2 GB+ RAM recommended, 1 GB only with swap) or official hosted. Everything else is a support problem.
- The installer tunes
UNICORN_WORKERSanddb_shared_buffers, provisions Let’s Encrypt, and can skip SMTP in favour of Discourse ID. Rebuilds are a 5–10 minute window after you editapp.yml. - Ports 80/443 must be free.
*.discourse.diycodes last 10 minutes. Own-domain production wants an A record that actually resolves. - Hosted 2026 public grid: Free, Pro $100/month, Business $500/month, Enterprise custom. Re-check discourse.org/pricing. Staff seats and plugin lists are the real gates, not the logo.
- Backups: Meta 14855. Weekly default, daily if you want, max 30-day frequency setting, keep copies off the VPS. S3 backups ≠ S3 uploads. Same bucket+folder will not work.
- fail2ban and unattended upgrades are the host-hardening the install guide actually names. A CDN is optional. Sharing the box with WordPress on 80/443 is not the official path.
- Hosted ↔ self-host is a backup restore, not an importer. Practice it once.
If the site is up, mail is honest, and last night’s backup restored on a scratch VPS, you are done with hosting. The next problems are categories, trust levels, and humans — which is to say, they are not hosting problems.

