Multiple Discourses, multiple containers, one server

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 community.oeru.org and forums.oeru.org, 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 
GRANT ALL PRIVILEGES ON DATABASE discourse2 TO discourse;
ALTER SCHEMA public OWNER TO discourse;
CREATE EXTENSION IF NOT EXISTS hstore;
CREATE EXTENSION IF NOT EXISTS pg_trgm;
END

and then exited:

CTRL-D

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:
  #  - "templates/redis.template.yml"
   - "templates/sshd.template.yml"
   - "templates/web.template.yml"
   - "templates/web.ratelimited.template.yml"

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

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

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

   ## TODO: configure connectivity to the databases
   DISCOURSE_DB_SOCKET: ''
   DISCOURSE_DB_USERNAME: discourse
   DISCOURSE_DB_PASSWORD: '[***discourse user password***]'
   DISCOURSE_DB_NAME: discourse2
   DISCOURSE_DB_HOST: data
   DISCOURSE_REDIS_HOST: data
   ##
   ## TODO: List of comma delimited emails that will be made admin and developer
   ## on initial signup example 'user1@example.com,user2@example.com'
   DISCOURSE_DEVELOPER_EMAILS: '[***my email***]'
   ##
   ## TODO: The domain name this Discourse instance will respond to
   DISCOURSE_HOSTNAME: 'forums.oeru.org'
   ##
   ## TODO: The mailserver this Discourse instance will use
   DISCOURSE_SMTP_ADDRESS: smtp.mandrillapp.com        # (mandatory)
   DISCOURSE_SMTP_PORT: 587                       # (optional)
   DISCOURSE_SMTP_USER_NAME: [***my email***]      # (optional)
   DISCOURSE_SMTP_PASSWORD: [***my password***] # (optional)
   DISCOURSE_SMTP_ENABLE_START_TLS: True
   ##
   ## The CDN address for this Discourse instance (configured to pull)
   #DISCOURSE_CDN_URL: //discourse-cdn.example.com

volumes:
   - 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.
 links:
   - link:
     name: data
     alias: data

 ## The docker manager plugin allows you to one-click upgrade Discouse
 ## http://discourse.example.com/admin/docker
 hooks:
   after_code:
      - exec:
        cd: $home/plugins
        cmd:
           - mkdir -p plugins
           - git clone https://github.com/discourse/docker_manager.git

## Remember, this is YAML syntax - you can only have one block with a name
run:
   - 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 127.0.0.1:8081 fail_timeout=5;
}

# configure the virtual host
server {
  listen  5.9.142.102:80;
  # replace with your domain name
  server_name forums.oeru.org;

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

  #Rewrite

  # 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;
  }

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

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

  # anything else not processed by the above rules:
  # * example.com/a will load example.com/a.html
  # * or if that fails, example.com/a/index.html

  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!

15 Likes

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 community.oeru.org included admin_[bighash].css?_ws=forums.oeru.org… 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?

2 Likes

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: https://meta.discourse.org/t/intermittent-stylesheet-errors-on-heroku-installation/26261/12

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.

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.

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

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

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?

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

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

1 Like

Hi all
I’m looking into something similare
I’m also wondering if this is the solution
my objective is to have one instance of discourse , with only one DNS
but to create many seperate independant home pages
as if each Organizatoin we manage arrives on it’s own discourse ,
while sharing the smae instance ?
is this possible ?

You could use multisite and subfolders. You could have one instance be the SSO server for all of them. But a subdomain for each makes more sense to me.

1 Like