SSD Nodes Learn 🎉 VPS from $5.50/mo
How to do am Matt ConnorBy Matt Connor · Updated 2026-08-13

How to Install Discourse on VPS with Docker

Install Discourse with the official Docker launcher, then set RAM, swap, real domain, SMTP, app.yml, rebuild, TLS and reverse proxy without guesswork.

Install Discourse for VPS: one container, one config file

To install Discourse for VPS, run the project own installer, answer small wizard questions, then wait for the build to finish. Discourse dey ship as one Docker container wey hold the Rails application, PostgreSQL, Redis, and nginx. Everything wey you go later change dey inside one file, /var/discourse/containers/app.yml, and every change go reach the site through rebuild.

The official install na discourse_docker: one launcher shell script plus some YAML templates. Discourse no support Compose file wey you write by yourself, and you no suppose split the container by hand. If you don used to run services for VPS with Docker Compose, expect different arrangement. No docker compose up -d dey here, and ./launcher rebuild app na the deploy.

Wetin Discourse need before you start

Four requirements dey catch people off guard, and each one fit cause wahala before you reach login page.

  • Memory. One container dey run PostgreSQL, Redis, Sidekiq and Ruby web server. Build step dey compile assets and e need more memory pass the site when e dey run.
  • Real domain name. The sample config wey come with am talk am clearly: "Discourse no go work with bare IP number."
  • Outbound mail path. Account activation, password resets, admin invites and digest mail all dey go through SMTP (simple mail transfer protocol).
  • Ports 80 and 443 must dey free for the host, unless you deliberately put Discourse behind proxy wey you already dey run.
ChartDiscourse published hardware requirements (official install docs, August 2026)
The data behind this chart
[
  {
    "label": "Documented minimum",
    "ram_gb": 1,
    "storage_gb": 10
  },
  {
    "label": "Documented recommended",
    "ram_gb": 2,
    "storage_gb": 20
  }
]

Official install document set the minimum at 1 GB of RAM with swap and 10 GB of disk, and e recommend 2 GB of RAM with 20 GB of disk. Read the first row as the number wey let installer finish, no be the number wey you want use to run community. The difference matter because memory peak dey happen during build, no be because of traffic.

Point the domain go the server before you install

Create A record for the hostname wey you go use, then confirm am from the server itself.

dig +short forum.example.com
curl -4 -s https://ifconfig.co

Both commands suppose print the same address. Dem must agree because setup wizard dey run connection test against your hostname, and record wey still point somewhere else go fail that test. A record wey you create two minutes ago fit still dey cache, so wait make the old TTL (time to live) pass instead of struggling with the wizard.

Decide now whether CDN go proxy the record. Proxied record dey hide your server address, and the container certificate request go fail because proxy go answer the ACME (automatic certificate management environment) challenge instead of Discourse. Keep the record unproxied for the first install.

Official installer run am

One command go install git, install Docker with Docker own install script, clone discourse_docker go /var/discourse, and start setup wizard.

wget -qO- https://raw.githubusercontent.com/discourse/discourse_docker/main/install-discourse | sudo bash

If Docker don already dey for the box and you prefer see each step, do the same work by hand.

sudo -s
git clone https://github.com/discourse/discourse_docker.git /var/discourse
cd /var/discourse
./discourse-setup

Run am as root. If ordinary user start am, discourse-setup go stop immediately with This script must be run as root. Please sudo or log in as root first.. If Docker no dey for the box, e go stop with Docker is not installed. Please install Docker first., because manual clone no install anything for you.

Wetin setup wizard dey ask, and wetin e dey write

As of August 2026 discourse-setup na thin wrapper. E dey run discourse/setup-wizard:release as container with host network and Docker socket mounted, so the wizard fit inspect the machine wey e dey configure. E dey ask for hostname and admin email addresses, then for your SMTP block. E dey write containers/app.yml, then rebuild.

Two behaviours good make you know before you start. If machine no get enough memory and e no get swap, wizard go stop and offer to create one: wrapper go make 2 GB /swapfile, add am to /etc/fstab, set vm.swappiness = 10 inside /etc/sysctl.d/30-discourse-swap.conf, then start wizard again. When wizard finish, e go print Rebuilding app in 5 seconds (Ctrl+C to cancel)... and run ./launcher rebuild app for host. That build fit take several minutes for small VPS, and the first one dey slow pass because every asset dey compile from scratch.

./discourse-setup --help dey list the flags wey matter when something no work. --skip-rebuild dey write config without building, and --skip-connection-test dey skip DNS and port checks. Use --skip-connection-test only when you already know why the test dey fail, for example when host dey behind network firewall wey you dey control.

Read app.yml before you rebuild for the first time

Wizard go write file wey na you go maintain from now. Open am with sudo nano /var/discourse/containers/app.yml. Na these parts dey decide almost everything.

templates:
  - "templates/postgres.template.yml"
  - "templates/redis.template.yml"
  - "templates/web.template.yml"
  - "templates/web.ratelimited.template.yml"
  ## Uncomment these two lines if you wish to add Lets Encrypt (https)
  #- "templates/web.ssl.template.yml"
  #- "templates/web.letsencrypt.ssl.template.yml"

expose:
  - "80:80"   # http
  - "443:443" # https

env:
  DISCOURSE_HOSTNAME: "forum.example.com"
  DISCOURSE_DEVELOPER_EMAILS: "you@example.com"
  DISCOURSE_SMTP_ADDRESS: smtp.example.com
  DISCOURSE_SMTP_PORT: 587
  DISCOURSE_SMTP_USER_NAME: user@example.com
  DISCOURSE_SMTP_PASSWORD: "your-smtp-password"

DISCOURSE_HOSTNAME na the address wey site dey answer for, and Discourse dey use am build links. So, wrong value fit make site load once, then redirect you go another place. DISCOURSE_DEVELOPER_EMAILS na comma-separated list. The addresses inside am go become admin automatically when person sign up for the first time. Put your own address there and register with am, because na so the first admin account dey create.

The file dey store your SMTP password as plain text, so restrict the directory with sudo chmod 700 /var/discourse/containers. The file na YAML too, so whitespace dey act as config: if key no align well, build go fail with parse error and site no go come up. One common trap dey documented inside the sample file itself. # inside password wey no get quotes go start comment, so put quotes around any password wey contain am.

Email na the step wey dey stop most installs

As of August 2026, the wizard dey allow you skip SMTP and use Discourse ID logins instead, and app.yml get matching DISCOURSE_SKIP_EMAIL_SETUP switch, wey e describe as skipping email setup validation. Skipping am make sense if na first time you dey check the software. But e no good for community, because if outbound mail no dey, nobody fit activate account or reset password.

The main problem be say most VPS providers block outbound port 25, so plain mail server for the box no go deliver mail. Use authenticated relay for port 587, or port 465 with implicit TLS (transport layer security). For port 465, set DISCOURSE_SMTP_FORCE_TLS: true, as the sample config recommend for that port. Test reachability from the host before you rebuild.

nc -vz smtp.example.com 587

Healthy result na one line wey end with succeeded!. If command hang, then timeout later, e mean say port dey blocked for the path comot from your VPS, and no Discourse setting fit fix am. Move go port wey your provider allow, or ask the provider make e open the port.

After site don come up, send test message from Email page for Admin, then read Skipped and Bounced tabs for that same page. Na those tabs Discourse dey record mail wey e refuse to send and mail wey relay reject. Dem also show the reason, so e faster than reading logs.

TLS: make the container get e own certificate

If Discourse own ports 80 and 443, use the issuance wey e get inside. Uncomment the two SSL template lines wey dey above, then rebuild. The template control acme.sh, store certificates for the shared volume under /shared/ssl, renew dem on schedule inside the container, and set Discourse to force HTTPS.

Port 80 must remain reachable from internet for this to work, because na there HTTP challenge dey get answer. Firewall wey allow only 443 go give you build wey finish, but certificate no go ever issue. Check the result with ./launcher logs app immediately after the rebuild.

Nginx or Caddy dey necessary for front?

If Discourse na the only web service for the VPS, no use am. The container already dey run tuned nginx, and second proxy go add another hop, another certificate wey you go renew, plus new place wey header bugs fit come from.

Use am for front when the same VPS dey serve other sites. Add templates/web.socketed.template.yml to the templates list, comment out both expose lines, and leave the two SSL templates commented out. The container go then listen on a unix socket for /var/discourse/shared/standalone/nginx.http.sock and e no go hold any port, so ports 80 and 443 go free for your own proxy.

server {
  listen 443 ssl;
  server_name forum.example.com;

  location / {
    proxy_pass http://unix:/var/discourse/shared/standalone/nginx.http.sock:;
    proxy_set_header Host $http_host;
    proxy_http_version 1.1;
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Real-IP $remote_addr;
  }
}

The colon wey dey after .sock na part of nginx unix socket syntax, and sudo nginx -t go reject the config if the colon no dey. X-Forwarded-Proto no be optional too. Discourse dey write absolute links, so if that header no dey, e go generate http:// links for an HTTPS page, and browsers go block dem as mixed content. Once the container dey use socket, TLS become your responsibility, so issue the certificate for the host with Certbot for Ubuntu 24.04 and nginx. If you never choose proxy, the comparison between nginx, Caddy and Traefik explain the trade-off wey you dey make.

Rebuild, upgrade, and the commands wey you go really use

cd /var/discourse
./launcher rebuild app

rebuild dey destroy the container wey dey run, build new one from app.yml, then start am. The site no go dey available throughout the build, so treat every config change as scheduled downtime of some minutes.

If you change only values under env:, you no need do that. ./launcher destroy app && ./launcher start app dey recreate the container from the image wey you don already build, and e dey take seconds. Anything under templates: or hooks: dey change the image itself, so e need the full rebuild.

Upgrades dey come through two ways. You fit apply point releases from the web interface for /admin/upgrade, through the docker_manager plugin wey app.yml clone during the build. Changes to the base image or the templates dey come from git.

cd /var/discourse
git pull
./launcher rebuild app

Na during rebuild small servers dey often fail, because asset compilation na when the whole system dey use the most memory. If build stop halfway, and dmesg show line like Out of memory: Killed process wey name a ruby process, memory don finish during the build, even though the site dey work fine before. Add swap, then run the rebuild again.

./launcher logs app
./launcher enter app
./launcher cleanup

logs dey print the container output, enter dey open shell inside am, and cleanup dey remove containers wey don stop for more than 24 hours. Run cleanup from time to time, because every rebuild dey leave old container behind, and disk space for small VPS fit finish quietly.

Backups, and the file wey backup no contain

Take backups from the Backups page for Admin. The archive go land for host at /var/discourse/shared/standalone/backups/default/. You fit run the same job from shell.

cd /var/discourse
./launcher enter app
discourse backup

discourse restore <filename> dey reverse am, and restore no go work until you run discourse enable_restore. This guard dey prevent stray command from overwriting live forum.

You need close two gaps yourself. The archive get database, and e get uploaded files only when backup setting wey include uploads dey on, so check that setting before you trust am. E no ever get app.yml, so restore for fresh VPS still need your hostname and SMTP block. This mean say you also need copy that file comot from the box.

The archive still dey for the same disk with the site wey e dey protect. That one no be backup. Pull am go another place on schedule.

rsync -avz root@forum.example.com:/var/discourse/shared/standalone/backups/default/ ~/discourse-backups/

Wetin busy forum dey cost for RAM

Bootstrap dey set UNICORN_WORKERS and db_shared_buffers from the memory and CPU wey e detect, and the sample config dey limit shared buffers to one-quarter of total memory. Each unicorn worker na complete Ruby process, and Sidekiq dey run background jobs beside dem, so memory use dey follow concurrent requests, no be the number of registered members. Quiet forum wey get few hundred members no be heavy workload.

No size the server from number wey you see for article, including this one. Measure your own server.

free -m
docker stats --no-stream

If Swap dey constant use together with slow pages, RAM no dey enough. If memory stay steady but pages still slow, usually na another thing cause am, so read ./launcher logs app before you buy bigger plan. Add check from outside the box too, because forum wey memory finish for 3am fit fail quietly: self-hosted Uptime Kuma status monitor for separate host go tell you before your members notice.

When Discourse no be the right choice

Discourse na large application with heavy installation and rebuild cycle for every setting wey dey inside app.yml. This cost dey give you proper moderation tools and search wey still dey work when archive don large. For thirty people wey just want place to talk, e pass wetin the conversation need. First read comparison of self-hosted forum software, then choose Discourse because you want wetin e fit do, no be because na the name you already know.

FAQ

I fit install Discourse for VPS without domain name?

No. The shipped configuration talk say Discourse no go work with bare IP number, and DISCOURSE_HOSTNAME dey required. Discourse dey build absolute links from that hostname, so IP address for there go break links and stop certificate issuance. Create an A record before you start, and confirm with dig +short forum.example.com say e resolve to your server address.

I must configure SMTP to finish the install?

As of August 2026, you fit skip am. The setup wizard dey offer Discourse ID logins instead, and app.yml get switch wey skip email setup validation. For anything beyond first look, configure am, because account activation and password resets both dey go through mail. Use authenticated relay for port 587 or 465, because most VPS providers dey block outbound port 25.

Why Discourse rebuild fail halfway?

Memory na the usual cause. Asset compilation during the build need more memory than the running site, so box wey dey serve the forum fine fit still fail to rebuild am. If dmesg show Out of memory: Killed process wey name ruby process, add swap (the wizard own swapfile na 2 GB) and run ./launcher rebuild app again. Build wey stop because of YAML error dey point instead to indentation mistake inside app.yml.

Discourse suppose dey behind my own nginx or Caddy?

Only when the VPS dey serve other sites too. If na only Discourse dey run for the server, make the container keep ports 80 and 443 and issue its own certificate. This one leave fewer moving parts. To share the machine, add templates/web.socketed.template.yml, comment out the expose lines, and proxy to the unix socket for /var/discourse/shared/standalone/nginx.http.sock. Pass X-Forwarded-Proto through, or Discourse go emit http:// links for HTTPS page.

How I fit back up self-hosted Discourse?

Use the Backups page for Admin, or run discourse backup after ./launcher enter app. Archives dey land for the host at /var/discourse/shared/standalone/backups/default/. Confirm say the setting wey includes uploads dey on, copy /var/discourse/containers/app.yml together with the archive, and move both go another machine, because backup wey dey for the same disk with the site no go survive the failure wey e suppose protect against.