How to Install Discourse on VPS with Docker
Set up Discourse with the official Docker launcher, app.yml and rebuild. Check RAM, swap, real domain, SMTP, TLS and reverse proxy requirements before you start.
Install Discourse for VPS: one container, one config file
To install Discourse for VPS, you go run the project own installer, answer small wizard, then wait make e build. Discourse releases as one Docker container wey hold the Rails application, PostgreSQL, Redis and nginx. Everything wey you go change later dey inside one file, /var/discourse/containers/app.yml, and every change go reach the site through rebuild.
The official install na discourse_docker: launcher shell script plus some YAML templates. Discourse no support Compose file wey you write by yourself, and dem no design the container make you split am by hand. If you don used to running services for VPS with Docker Compose, expect different structure. 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 problem before you reach the login page.
- Memory. One container dey run PostgreSQL, Redis, Sidekiq and a Ruby web server. The build step dey compile assets and e need more memory than the site dey need while e dey run.
- A real domain name. The sample config wey ship with am talk am clearly: "Discourse no go work with bare IP number."
- An outbound mail path. Account activation, password resets, admin invites and digest mail all dey go out 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.
Di data wey dey behind dis chart
[
{
"label": "Documented minimum",
"ram_gb": 1,
"storage_gb": 10
},
{
"label": "Documented recommended",
"ram_gb": 2,
"storage_gb": 20
}
]The 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 go let the installer finish, no be the number wey you want use to run community. The difference matter because the highest memory use na during build, no be from traffic.
Point domain go the server before you install am
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.coBoth commands must print the same address. Dem gree because setup wizard dey run connection test against your hostname, and record wey still dey point somewhere else go fail that test. A record wey you create two minutes ago fit still dey cached too, so wait make 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 dey answer the ACME (automatic certificate management environment) challenge instead of Discourse. Keep the record unproxied for the first install.
Run official installer
One command go install git, install Docker with Docker own install script, clone discourse_docker enter /var/discourse, and start setup wizard.
wget -qO- https://raw.githubusercontent.com/discourse/discourse_docker/main/install-discourse | sudo bashIf 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-setupRun 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.
Wetín the setup wizard dey ask, and wetin e dey write
As of October 2026 discourse-setup na just thin wrapper. E dey run discourse/setup-wizard:release as container with the host network and Docker socket mounted, so the wizard fit inspect the machine wey e dey configure. E dey ask for the hostname and admin email addresses, then e ask for your SMTP block. E dey write containers/app.yml, then rebuild am.
Two behaviours dey important to know before you start. If the machine memory no plenty and e get no swap, the wizard go stop and offer to create swap: the wrapper go make 2 GB /swapfile, add am to /etc/fstab, set vm.swappiness = 10 for /etc/sysctl.d/30-discourse-swap.conf, and start the wizard again. When the wizard finish, e go print Rebuilding app in 5 seconds (Ctrl+C to cancel)... and run ./launcher rebuild app for the host. This 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 lists the flags wey matter when something no dey work. --skip-rebuild writes the config without building, and --skip-connection-test skips the DNS and port checks. Use --skip-connection-test only when you already know why the test dey fail, for example when the host dey behind network firewall wey you control.
Read app.yml before the first rebuild
The wizard write one file wey na now your responsibility to maintain. 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 the site dey answer on, and Discourse dey use am build im links. So, if the value wrong, the site fit load once, then send you go another place. DISCOURSE_DEVELOPER_EMAILS na comma-separated list, and those addresses go automatically become admin during first signup. Put your own address there and register with am, because na so the first admin account dey created.
The file dey store your SMTP password as plain text, so restrict the directory with sudo chmod 700 /var/discourse/containers. The file still be YAML, and for YAML whitespace na configuration: if key no align well, the build go fail with parse error and you no go get site. One 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 October 2026, the wizard dey allow you skip SMTP and use Discourse ID logins instead. app.yml carry matching DISCOURSE_SKIP_EMAIL_SETUP switch too, and e describe am as skipping email setup validation. You fit skip am if you just wan first look at the software. But e no good for community, because if outbound mail no dey, nobody fit activate account or reset password.
The practical problem be say most VPS providers dey 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 587Healthy result na one line wey end with succeeded!. If command hang, then timeout later, e mean say port dey blocked for the path out of your VPS. No Discourse setting fit fix that. Move to port wey your provider allow, or ask the provider to open am.
Once site don come up, send test message from Email page inside Admin. Then read Skipped and Bounced tabs for that same page. Na for those tabs Discourse dey record mail wey e refuse to send and mail wey relay reject. Dem go also show the reason, wey faster than reading logs.
TLS: make container get im own certificate
If Discourse dey own ports 80 and 443, use im built-in issuance. Remove comment from the two SSL template lines wey show above, then rebuild. The template dey control acme.sh, store certificates inside the shared volume under /shared/ssl, renew dem according to 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 for front use?
If Discourse na be the only web service for the VPS, no use am. The container don already run tuned nginx. Another proxy go add one more hop, another certificate to renew, and another 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 unix socket for /var/discourse/shared/standalone/nginx.http.sock and e no go hold any port, so port 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. sudo nginx -t go reject the config if the colon no dey there. X-Forwarded-Proto no be optional too. Discourse dey write absolute links, so if that header no dey, e go generate http:// links for HTTPS page, and browsers go block dem as mixed content. Once the container dey use socket, TLS na your responsibility. Issue the certificate for the host with Certbot for Ubuntu 24.04 and nginx. If you never choose proxy yet, comparison of nginx, Caddy and Traefik explain the trade-off.
Rebuild, upgrade, and commands wey you go really use
cd /var/discourse
./launcher rebuild apprebuild dey stop the container wey dey run, bootstrap new one from app.yml, then remove the old container and start the new one. The site no dey online throughout the build, so treat every config change as scheduled downtime wey fit last some minutes.
If na only values under env: you change, you no need do that. ./launcher destroy app && ./launcher start app dey recreate the container from the image wey you don already build, and this one dey take seconds. Anything under templates: or hooks: dey change the image itself, so e need the full rebuild.
Upgrade dey come in two ways. You fit apply point releases from the web interface for /admin/upgrade, wey docker_manager plugin provide. app.yml dey clone this plugin during the build. Changes to the base image or templates dey come from git.
cd /var/discourse
git pull
./launcher rebuild appNa rebuild dey make small servers fail, because asset compilation na the point wey 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 run fine before. Add swap, then run the rebuild again.
./launcher logs app
./launcher enter app
./launcher cleanuplogs dey print the container output, while enter dey open shell inside am. cleanup dey remove stopped containers and every unused image wey don pass one hour old, and Docker go ask you to confirm each prune. The launcher's help text still talk say e dey remove containers wey don stop for more than 24 hours, but as of October 2026, the command dey use 1-hour filter. Run cleanup from time to time, because every rebuild dey leave the previous image 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 backupdiscourse restore <filename> go reverse am, and restore no go work until you run discourse enable_restore. This guard dey there so stray command no fit overwrite live forum.
You go 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 to fresh VPS still need your hostname and SMTP block. This means say you must copy that file comot from the box too.
The archive still dey for the same disk with the site wey e 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 alongside dem. So memory use dey follow concurrent requests, no be number of registered members. Quiet forum wey get few hundred members no be heavy workload. Wetin else dey share the server usually matter pass. If na photo library, the RAM minimums wey dem measure for the PhotoPrism and Immich comparison go show whether Discourse rebuild still get enough headroom to finish.
No size the server from number wey you see for any article, including this one. Measure your own system.
free -m
docker stats --no-streamIf swap dey constant use together with slow pages, RAM no reach. If memory dey steady but pages slow, the problem usually dey somewhere else, so read ./launcher logs app before you buy bigger plan. Add check from outside the server too, because forum wey run out of memory for 3am fit fail quietly: self-hosted Uptime Kuma status monitor for separate host go alert you before your members notice.
When Discourse no be the correct choice
Discourse na large application wey get heavy installation and rebuild cycle for every setting wey dey inside app.yml. This cost give you real moderation tools and search wey still dey work when archive don big. For thirty people wey just want somewhere to talk, the application too big for wetin the conversation need. Read comparison of self-hosted forum software first, 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 A record before you start, then confirm with dig +short forum.example.com say e resolve to your server address.
I must configure SMTP to finish the install?
As of October 2026 you fit skip am. The setup wizard dey offer Discourse ID logins instead, and app.yml get switch wey dey skip email setup validation. For anything beyond first look, configure am, because account activation and password reset 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 server wey dey serve the forum well 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. If build stop because of YAML error, e likely mean say indentation mistake dey inside app.yml.
Discourse suppose dey behind my own nginx or Caddy?
Only if 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 reduce the 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 go 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, then 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.