Production guide

Self-hosting Discourse in production (2026): hardware, app.yml, runbook

What it actually costs, what it actually breaks, and what you actually need to know before you commit. Hardware sizing benchmarks across four VPS tiers (measured, not guessed), the app.yml mental model, an honest cost comparison versus Discourse cloud, and a 15-point runbook you can paste into your ops doc.

community.example.com / sidekiq
SimpleReview
Sidekiq dashboard
Workers / Queues / Retries
Processed
128,402
Failed
14
Busy
5/5
Enqueued
8,194
[default] Jobs::CleanUpUploads · 0.4s
[low] Jobs::EmitWebHookEvent · 1.2s
[critical] Jobs::ProcessPost · queued for 47 minutes — worker pool saturated
[default] Jobs::PostAlert · 0.2s
[low] Jobs::PullHotlinkedImages · timeout
SimpleReview
.sc-log-row.error
containers/app.yml:18
Sidekiq queue keeps backing up — bump UNICORN_SIDEKIQS to 4 and rebuild?
Fix #214 ready · 1 file changed
+1 −1 in containers/app.yml
SimpleReview reads a stuck Sidekiq queue, drafts the app.yml tweak, prepares a site fix — production runbook in motion.
TL;DR
  • Hardware: $12/mo (4 GB / 2 vCPU) is the sweet spot up to ~5k DAU; rebuilds need ~1.5 GB free ON TOP of running Discourse
  • app.yml mental model: Discourse builds a custom image per site via ./launcher — never docker run discourse/discourse
  • Cost: below ~1k DAU self-host wins; above ~10k DAU your engineering time crosses cloud pricing
  • Runbook: 15 items — backups, mail, SSL, plugin pinning, Sidekiq monitoring
  • ARM works. Hetzner CAX and Oracle Free Tier run Discourse fine on arm64

Hardware sizing — measured, not guessed

Every "Discourse system requirements" post on the internet recites the same 2 GB RAM minimum. That's true but useless. Here's what we actually measured on each VPS tier with a synthetic load of 200 concurrent users browsing latest, plus image-baking and Sidekiq jobs running in the background.

VPS tierRAM / vCPUIdle Discourse200-user browseVerdict
$5/mo (Hetzner CX11, DO basic) 2 GB / 1 vCPU RAM: 1.7 GB used. Sidekiq backs up. p95 latency: 1.2-2.1 s. Image-bake jobs queue. Hobby only. Don't run mail-heavy or media-heavy forums here.
$12/mo (Hetzner CX21, DO 2GB-shared) 4 GB / 2 vCPU RAM: 1.8 GB. Headroom for Postgres cache. p95 latency: 380-520 ms. Sidekiq stays drained. Sweet spot for forums up to ~5k DAU.
$24/mo (Hetzner CX31, DO 4GB-dedicated) 8 GB / 2-4 vCPU RAM: 2.1 GB. Postgres warm cache > 1 GB. p95 latency: 220-310 ms. CPU plateau ~35%. Comfortable up to ~15k DAU before splitting Postgres.
$48/mo (Hetzner CX41, DO 8GB) 16 GB / 4 vCPU RAM: 2.3 GB. Plenty of cache headroom. p95 latency: 180-240 ms. Sidekiq parallelism stops being the bottleneck. You're paying for cache, not CPU. Probably time to split Postgres.

ARM works. Hetzner's CAX line and Oracle Free Tier ARM both run Discourse fine — image_optim binaries are compiled for arm64 in the official image, Sidekiq/Postgres/Redis don't care. Bench it before committing because Ruby/Rails performance per ARM vCPU is ~85-90% of x86_64 in our tests, but cost per request is better.

Pitfall
If you size for idle RAM and forget the rebuild — ./launcher rebuild app needs ~1.5 GB free ON TOP of running Discourse, because it builds a new container alongside the old one. On a 2 GB VPS you'll OOM mid-rebuild. Two fixes: a 2 GB swap file (slow but safe), or stop the container before rebuild (downtime).

The app.yml mental model

Most newcomers see discourse/discourse on Docker Hub and try docker run -d -p 80:80 discourse/discourse. Don't. That image is the build base, not the application.

Discourse uses an unusual deploy pattern: discourse_docker (a separate repo) contains a Bash launcher that:

  1. Reads containers/app.yml — your declarative config (env vars, hooks, plugins, hostname)
  2. Pulls the discourse/base image
  3. Runs ./launcher bootstrap — boots the base, runs the hooks (which install plugins, configure Postgres, set the hostname, build assets), then commits the resulting state as a new image
  4. Runs ./launcher start — runs that committed image as the live container

The result: every site has a custom-built image. Plugins and config live in the image, not as runtime mounts. This is why adding a plugin requires ./launcher rebuild app — you're literally building a new image with the new plugin baked in.

Trade-off: rebuilds are slower than docker compose updates (5-15 min depending on plugins), but the production runtime is dead simple — one container, no orchestration, no service mesh, no init system. The whole stack lives inside one Docker container — Postgres, Redis, Sidekiq, Rails, image_optim binaries.

# containers/app.yml — minimal production excerpt
templates:
  - "templates/postgres.template.yml"
  - "templates/redis.template.yml"
  - "templates/web.template.yml"
  - "templates/web.ratelimited.template.yml"

expose:
  - "80:80"
  - "443:443"

params:
  db_default_text_search_config: "pg_catalog.english"
  db_shared_buffers: "256MB"

env:
  LANG: en_US.UTF-8
  UNICORN_WORKERS: 4
  UNICORN_SIDEKIQS: 2
  DISCOURSE_HOSTNAME: 'community.example.com'
  DISCOURSE_DEVELOPER_EMAILS: '[email protected]'
  DISCOURSE_SMTP_ADDRESS: 'smtp.mailgun.org'
  DISCOURSE_SMTP_PORT: 587
  DISCOURSE_SMTP_USER_NAME: '[email protected]'
  DISCOURSE_SMTP_PASSWORD: ''
  LETSENCRYPT_ACCOUNT_EMAIL: '[email protected]'

hooks:
  after_code:
    - exec:
        cd: $home/plugins
        cmd:
          - git clone https://github.com/discourse/docker_manager.git
          - git clone https://github.com/discourse/discourse-akismet.git

run:
  - exec: echo "Beginning of custom commands"

Once you understand this, the rest of Discourse ops becomes obvious: plugins go in hooks: after_code, mail credentials are env vars, hostname changes mean a rebuild. The full reference for these directives lives in the discourse_docker repo's samples/ directory.

Self-host vs Discourse cloud — the honest comparison

Discourse, Inc. sells managed hosting starting at $100/mo (Standard) and scaling by user count. Their pitch: zero ops, automatic upgrades, premium support, an SLA. Real comparison:

Decision factorSelf-hostedDiscourse cloud
Sticker cost $5-48/mo VPS + $5-20 mail + $0-15 storage = ~$15-80/mo $100/mo Standard, $300/mo Business, custom Enterprise
Upgrades Manual ./launcher rebuild app when /admin/upgrade shows new version Automatic, with safety canary deploys
Plugins Anything you can git clone — community plugins, custom forks, internal plugins Curated set; custom plugins on Business+ tiers only
Mail deliverability You configure Mailgun/Postmark/SES yourself; you babysit DKIM/SPF/DMARC Configured by Discourse on hosted IPs; bounce handling included
Backup & restore Built-in nightly backup, S3 upload optional. You verify the restore flow. Daily off-site backups with one-click restore
Time to first incident ~3 months (a Sidekiq backlog or a failed rebuild) Effectively zero from your side

Decision rule we'd actually use: below ~1k DAU and a dev team comfortable with Linux ops, self-host wins on price and control. Above ~10k DAU or for a non-technical owner, the time cost of self-hosting crosses Discourse's pricing and cloud is the rational choice. Between those two — depends on whether you want custom plugins (self-host) or you want to never page yourself at 3 a.m. (cloud).

The 15-point production runbook

Discourse runbook v2026

  • VPS sized for rebuilds (idle RAM × 1.5, +2 GB swap)
  • Hostname DNS points to server, A + AAAA records, low TTL until cutover
  • app.yml in version control (private repo) — never edit on server only
  • Mail provider: Mailgun / Postmark / SES creds in env vars; SPF, DKIM, DMARC published
  • DISCOURSE_DEVELOPER_EMAILS includes you AND a backup admin
  • Backup daily, with S3/Backblaze upload — verify restore on a staging container monthly
  • letsencrypt.template.yml included so cert auto-renews
  • Sidekiq dashboard accessible at /sidekiq — only behind admin auth
  • Monitoring: uptime check on /srv/status, alert on Sidekiq queue depth > 1000 for 10 min
  • Upgrade plan: subscribe to meta.discourse.org #release-notes, plan rebuilds for low-traffic windows
  • Plugins: each one tracks a fork pinned to a SHA — never a moving main branch
  • Reply-by-email: at least the ./launcher mailtest green; full setup
  • SSO if you have it: test signing in staging first
  • Custom plugins: tested with bin/rspec plugins/my-plugin/spechow-to
  • API integrations: rate-limit aware, signed webhooks — recipes

What's intentionally out of scope

Things this guide doesn't cover and where to find them:

Frequently asked

Is self-hosting Discourse cheaper than the official cloud?
Below ~1k DAU and with internal Linux ops capacity — yes. A $12/mo VPS plus $10/mo Mailgun plus $5/mo Backblaze B2 is roughly $27/mo against the $100/mo Standard plan, before any plugin-tier upgrades. Above ~10k DAU, your engineering time crosses cloud pricing.
What hardware does Discourse need in 2026?
Minimum: 2 GB RAM, 2 CPU cores, 20 GB disk plus 2 GB swap. Comfortable for production: 4 GB RAM, 2-4 vCPU, 40 GB SSD. The whole stack — Rails, Sidekiq, Postgres, Redis — runs in a single Docker container that idles around 1.4-1.8 GB RAM and grows under image-baking jobs. ARM (Hetzner CAX, Oracle Free Tier) works fine.
Why can't I just docker run discourse/discourse?
The discourse/discourse image is a build base, not the application. Discourse uses an unusual deploy pattern via the discourse_docker repo — it reads containers/app.yml, pulls discourse/base, runs your hooks (plugins, config, hostname), and commits the result as a new image. Every site has a custom-built image. Use ./launcher bootstrap app && ./launcher start app.
Can I run Discourse without Docker?
Officially unsupported, but possible. The container gives you Postgres, Redis, Sidekiq, Rails, and image_optim binaries pinned to versions Discourse tests against. Running outside it means rolling those yourself and patching by hand. People do it on FreeBSD or weird ARM boards, accepting that things like image_optim and the scheduled-job daemon may break. We recommend Docker.
How big a VPS do I need for 1000 daily users?
$12/mo (4 GB RAM, 2 vCPU) is the sweet spot for forums up to ~5 000 DAU. Below that you have headroom; above that consider $24/mo (8 GB RAM) or split Postgres to a separate host once Postgres warm cache exceeds available RAM.
Does ARM work?
Yes — Hetzner CAX line and Oracle Free Tier both run Discourse fine. image_optim binaries are compiled for arm64 in the official image; Postgres, Redis, Sidekiq are all architecture-agnostic. Per-vCPU Ruby/Rails performance on ARM is ~85-90% of x86_64 in our tests, but cost per request is better.