Running multiple Discourse sites on a single server
I used to host comments on a free Discourse server, but after starting a consulting business, I've decided to move to a paid service. At the moment I'm using DigitalOcean because I can get a virtual machine capable of running Discourse for $12 a month. It's also easy to spin up new machines (AKA Droplets) and only pay for the time they are active. I got $200 credits for the first 2 months in order to try out the service. (If you want get the same deal, use my referal link.)
Now that the trial period is running out, I've decided to consolidate my test servers onto one virtual machine in order to save money. At the moment, I have:
beta.jlericson.com
, which is a copy of my meta site for staging.beta.buildcivitas.com
, which is a secret project I'm starting.1
One solution would be to set up a completely separate Docker container for each site. Another is to create multiple sites on a single Docker container. Those instructions note:
Also, if you plan to run HTTPS, you will need a proxy in front of the site to handle it as the built in letsencrypt functionality will not work in a multisite scenario.
I do want HTTPS, for obvious reasons. Having a proxy server also allows me to set up a page for when Discourse is down for whatever reason. A drawing shows what I'm aiming for:
Both of the URLS are linked to a single DigitalOcean Droplet by setting up DNS A records pointing to the same IP. Then a proxy Nginx server outside the Docker container routes traffic via a web socket to Discourse. Finally Discourse selects which database2 to read based on which host was in the requested URL.
The big advantage of this setup is that we can run on a single Droplet without the overhead of running a second Docker container. The more forums you want to host, the bigger the potential savings. The downside is that both sites need to be updated in tandem, share email settings and have the same set of plugins. For small forums, this is not likely to be a problem.3
Start by setting up the proxy
I used the standard Docker
install
to set up a new Discourse instance. That way I could have a simple
test without risking any data I cared about. Then I restored a recent
backup from my comment site to become beta.jlericson.com
. Next I
used these
instructions
to install Nginx and change app.yml to:
- Remove SSL related templates,
- Add in `- "templates/web.socketed.template.yml" and
- Remove any exposed ports.
At this point I did a sudo /var/discourse/launcher rebuild app
to
make sure the settings worked and I hadn't messed up the YAML
formatting.
I configured /etc/nginx/sites-available/beta.jlericson.com
4 so
that Nginx passes along traffic to the web socket with the proper
Host
header set:
server_name beta.jlericson.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 $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
}
This rule only applies to traffic headed toward
beta.jlericson.com
and it uses a special file Discourse creates for
web socket traffic. With this in place, it's just a matter of running
certbot --nginx
and checking with your browser that the proxy
redirects traffic to your Discourse site. Don't move on until you
know this part is working properly!
Add a second site to Discourse
The official instructions give just enough detail to figure out what's going on. Instead of running multiple Discourse servers, the multisite system creates a second database that represents all the data needed to run a second site. There's no logical limit to the number of sites that can be served this way, though it's not hard to imagine running out of various resources along the way.
The first change is to create a database for beta.buildcivitas.com
called civitas
:
hooks:
after_postgres:
- exec: sudo -u postgres createdb civitas || exit 0
- exec:
stdin: |
grant all privileges on database civitas to discourse;
cmd: sudo -u postgres psql civitas
raise_on_fail: false
- exec: /bin/bash -c 'sudo -u postgres psql civitas <<< "alter schema public owner to discourse;"'
- exec: /bin/bash -c 'sudo -u postgres psql civitas <<< "create extension if not exists hstore;"'
- exec: /bin/bash -c 'sudo -u postgres psql civitas <<< "create extension if not exists pg_trgm;"'
Then it creates multisite.yml
configuration that directs
beta.buildcivitas.com
traffic to the new civitas
database inside
the Docker container:
before_bundle_exec:
- file:
path: $home/config/multisite.yml
contents: |
civitas:
adapter: postgresql
database: civitas
pool: 25
timeout: 5000
db_id: 2
host_names:
- beta.buildcivitas.com
Finally, when the app is rebuilt it needs to migrate data from the old container to the new one:
after_bundle_exec:
- exec: cd /var/www/discourse && sudo -E -u discourse bundle exec rake multisite:migrate
Once again, rebuild the app before moving on.
Add the second site to the proxy
Finally, I copied /etc/nginx/sites-available/beta.jlericson.com
to
/etc/nginx/sites-available/beta.buildcivitas.com
and update contents
to have the proper server name. I also made sure a DNS lookup of both
sites point to the same Droplet. When I was done, all I needed to do
was re-run the certbot
to create an SSL certificate for the second
site.
Testing both sites
For some reason, my first account on beta.buildcivitas.com
wasn't an
admin for some reason. So I needed to use the multisite directions to
update my
account. In my case, I used:
RAILS_DB=civitas rake admin:create
Than I changed some settings on the new site and verified that things seemed to be working. So far so good!
-
Nothing to see just yet. I'm hoping to tease more soon. ;-) ↩
-
In this setup, both sites are hosted on the same PostgreSQL database server, which runs in the same container as Discourse. If this were Oracle, I'd say "Discourse selects which scheme". ↩
-
Hosting a test server in the same container as a production system would be a problem. I'm eventually going to bring
meta.jlericson.com
to the same Droplet and I suspect the solution will be to set up a new container behind the outside nginx server. ↩ -
I tried using config.d rather than sites-engabled, but it didn't seem to work with
cerbot
for whatever reason. ↩
If you want to talk with me about community management schedule a meeting!