Multiple Discourses, multiple containers, one server

(Dave Lane) #1

Despite at least one reference which purports to describe the process I found it very difficult to work out how to run multiple Discourse instances on the same hosting infrastructure (using an instance of nginx running on the host to proxy the Discourse instances).

Apparently there’s a way to run Discourse in a “multisite” mode, with multiple instances from a single code installation, but I have not yet seen a real description of how that works.

I’ve gone the route of two independent code installations, but a common data container (two separate databases on the same server). After a lot of fiddling, I’ve now got two Discourse instances running and, with two separate Docker web containers and a single data container on a single server.

For the benefit of others wanting to achieve this, here’s how I did it:

1. Set up a new Postgres database in the data container - after trying to create the second database via my data.yml file (in containers dir), which didn’t seem to work - I logged directly into the container to create the second database (the launcher command is run from within the Discourse root directory, in my case /home/www/discourse):

./launcher enter data

and I ran the following (my second database is “discourse2”, and is owned by the same discourse user (and password) as my other Discourse instance, with db of “discourse”):

sudo -u postgres createdb discourse2

sudo -u postgres psql discourse2 <<END 
ALTER SCHEMA public OWNER TO discourse;

and then exited:


2. I restarted the data container (this seemed to be necessary, although I’m not sure why):
./launcher restart data

3. I created a configuration for a second web container (my first is just web.yml, so I called the second web2.yml) in which I put:

# IMPORTANT: SET A SECRET PASSWORD in Postgres for the Discourse User
# TODO: change SOME_SECRET in this template

  #  - "templates/redis.template.yml"
   - "templates/sshd.template.yml"
   - "templates/web.template.yml"
   - "templates/web.ratelimited.template.yml"

# increment the ports by one from the convention initiated by first discourse
   - "8081:80"
   - "2223:22"

  ## Which Git revision should this container use? (default: tests-passed)
  #version: tests-passed

   LANG: en_US.UTF-8
   ## TODO: How many concurrent web requests are supported?
   ## With 2GB we recommend 3-4 workers, with 1GB only 2

   ## TODO: configure connectivity to the databases
   DISCOURSE_DB_PASSWORD: '[***discourse user password***]'
   DISCOURSE_DB_NAME: discourse2
   ## TODO: List of comma delimited emails that will be made admin and developer
   ## on initial signup example ','
   DISCOURSE_DEVELOPER_EMAILS: '[***my email***]'
   ## TODO: The domain name this Discourse instance will respond to
   ## TODO: The mailserver this Discourse instance will use
   DISCOURSE_SMTP_ADDRESS:        # (mandatory)
   DISCOURSE_SMTP_PORT: 587                       # (optional)
   DISCOURSE_SMTP_USER_NAME: [***my email***]      # (optional)
   DISCOURSE_SMTP_PASSWORD: [***my password***] # (optional)
   ## The CDN address for this Discourse instance (configured to pull)

   - volume:
     host: /home/www/discourse/shared2/web
     guest: /shared
   - volume:
     host: /home/www/discourse/shared2/web/log/var-log
     guest: /var/log

 ## Use 'links' key to link containers together, aka use Docker --link flag.
   - link:
     name: data
     alias: data

 ## The docker manager plugin allows you to one-click upgrade Discouse
      - exec:
        cd: $home/plugins
           - mkdir -p plugins
           - git clone

## Remember, this is YAML syntax - you can only have one block with a name
   - exec: echo "Beginning of custom commands"

## If you want to configure password login for root, uncomment and change:
## Use only one of the following lines:
#- exec: /usr/sbin/usermod -p 'PASSWORD_HASH' root
#- exec: /usr/sbin/usermod -p "$(mkpasswd -m sha-256 'RAW_PASSWORD')" root

## If you want to authorized additional users, uncomment and change:
#- exec: ssh-import-id username
#- exec: ssh-import-id anotherusername

- exec: echo "End of custom commands"
- exec: awk -F\# '{print $1;}' ~/.ssh/authorized_keys | awk 'BEGIN { print "Authorized SSH keys for this container:"; } NF>=2 {print $NF;}'`

4. Build the new web container

./launcher bootstrap web2

and after it finishes

./launcher start web2

At this point, you should be able to check http://localhost:8081 on the host using a text browser like links or lynx to confirm that it’s actually producing something… Finally, setting up the nginx proxy.

5. Then I created the following file in /etc/nginx/sites-available (you can easily upgrade this configuration to use SSL and create another server definition to redirect traffic to port 80 to port 443 - now that we’ve been accepted to the Let’s Encrypt beta, I’ll be doing that shortly and will post an update afterwards):

upstream discourse2 {
#fail_timeout is optional; I throw it in to see errors quickly
  server fail_timeout=5;

# configure the virtual host
server {
  # replace with your domain name

  root /var/www; # placeholder only!
  index index.html;


  # assets
  location ~* \.(jpg|jpeg|gif|png|bmp|ico|pdf|txt|css|js) {
    try_files $uri @discourse2;
    add_header Cache-Control public;
    add_header Cache-Control must-revalidate;
    expires 7d;

  # gets redirected to
  location ~* \.html$ {
    rewrite ^(.*)\/index.html$ $1 permanent;
    rewrite ^(.+)\.html$ $scheme://$host$1 permanent;

  # loads
  location ~* ^(.*)/$ {
    try_files $1.html $1/index.html @discourse2;

  # anything else not processed by the above rules:
  # * will load
  # * or if that fails,

  location / {
    rewrite ^(.*)\/index$ $1 permanent;
    try_files $uri.html $uri/index.html @discourse2;

  location @discourse2 {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_pass http://discourse2;

And there you have it - our second Discourse instance, “forums”, is now available on the same host as our “community” Discourse instance… Hope this helps someone. Any feedback on this approach would be welcome!

Update private plugin without rebuilding the application
(Dave Lane) #2

I’ve now tried setting up SSL via a pair of nginx proxy configurations for the two Discourse instances. Unfortunately, this has been disastrous. I can get one or the other to work reliably, but not both, with very odd problems.

In each case, I have not changed the Discourse instance configurations (still a single data container with 2 databases, one for each instance, each with a separate web container), except that I’ve removed the scheme and host from the addresses of assets like the logos, etc. to make them https/http invariant (i.e. taking on the schema of the URL used to access the site which helps avoid “mixed content” warnings in security-conscious browsers like Firefox).

The problems I was having after enabling SSL proxying - i.e. two separate SSL certs specified in two configuration for the nginx instance running on the server with the 3 Discourse containers - included:

  • loss of CSS on both sites after enabling SSL on both - had a strange appended argument to the CSS string (to trigger browser cache expiry?) with the wrong site’s domain specified - in my case, the combined CSS file for included admin_[bighash].css?… this returned a 401.
  • swapping of the site logo files (despite having completely different asset upload URLs and filenames…) - very baffling.

These problems were corrected by

  1. disabling SSL on one of the sites, and
  2. restarting the containers ./launch restart web, etc.

Note that this same server has a number of other (non-Discourse) SSL sites, so it’s running in SNI mode. Anyone have any thoughts or insights?

(Dave Lane) #3

Ok, looks like the CSS 404s was not “fixed” by disabling SSL for one of the sites - it still periodically stops serving on either/both of the sites. A restart of the web container for either instance will temporarily reinstate the CSS. Seems to be a problem of the CSS aggregation or static file serving… I’ve posted about it here: Intermittent stylesheet errors on Heroku installation

I should note that I’m not aware of this problem (of 404s for CSS) arising prior to the introduction of the second discourse instance.

Discourse in Docker + NGINX reverse proxy + SSL everywhere + OAuth2 Custom
(omfg) #4

Do you mean from port 443 (externally on the host OS) to port 80 (in the two web containers)?
Your two web instances in Docker run on HTTP, while the host instance redirects from HTTPS to these two HTTP instances, right?

This sounds like a reasonable choice. I wouldn’t like to complicate things inside of the docker containers and see no need for HTTPS in there. If the host is compromised then it’s all over anyway.

(Dave Lane) #5

Sorry for the slow response. Yes, that’s what I am doing currently…

(Oka bRionZ) #6

I have a newbie question. Can I install for multiple-domain if using this method sir?

(Dave Lane) #7

You can have multiple Discourse instances on multiple domains using this model, yes, although I’m not sure that’s what you’re after… This is relatively complex to set up and each additional domain requires a fair bit of extra work… is that what you’re looking for?

(Oka bRionZ) #8

I want to install 3-5 domains in one server and all using discourse sir. Thanks for advice.

(Dave Lane) #9

You can certainly do it using the approach I used, probably worthwhile if you only want 3-5…