Through trials & errors, we finally managed to set up Discourse multisite with Letsencrypt SSL. Below is the step-by-step guide on how to implement it.
Note: Official Discourse does not support multiple SMTP for multisite, hence it is not possible to use different SMTP services for multisite installs.
-
During bootstrap, instead of following the first time set up prompt, press CTRL + C to exit and manually edit the app.yml using command:
nano containers/app.yml
-
Example content of manually edited app.yml:
this is the all-in-one, standalone Discourse Docker container template
After making changes to this file, you MUST rebuild
/var/discourse/launcher rebuild app
BE VERY CAREFUL WHEN EDITING!
YAML FILES ARE SUPER SUPER SENSITIVE TO MISTAKES IN WHITESPACE OR ALIGNMENT!
visit http://www.yamllint.com/ to validate this file as needed
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”which TCP/IP ports should this container expose?
If you want Discourse to share a port with another webserver like Apache or nginx,
see https://meta.discourse.org/t/17247 for details
expose:
- “80” # http
params:
db_default_text_search_config: “pg_catalog.english”Set db_shared_buffers to a max of 25% of the total memory.
will be set automatically by bootstrap based on detected RAM, or you can override
db_shared_buffers: “3584MB”
can improve sorting performance, but adds memory usage per-connection
#db_work_mem: “40MB”
Which Git revision should this container use? (default: tests-passed)
#version: tests-passed
env:
LANG: en_US.UTF-8DISCOURSE_DEFAULT_LOCALE: en
How many concurrent web requests are supported? Depends on memory and CPU cores.
will be set automatically by bootstrap based on detected CPUs, or you can override
UNICORN_WORKERS: 4
TODO: The domain name this Discourse instance will respond to
DISCOURSE_HOSTNAME: 'test1.domain.com’
VIRTUAL_HOST: 'test1.domain.com,test2.domain.com,test3.domain.com,test4.domain.com,test5.domain.com’
LETSENCRYPT_HOST: 'test1.domain.com,test2.domain.com,test3.domain.com,test4.domain.com,test5.domain.com’
LETSENCRYPT_EMAIL: ‘name@email.com’Uncomment if you want the container to be started with the same
hostname (-h option) as specified above (default “$hostname-$config”)
#DOCKER_USE_HOSTNAME: true
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: ‘name@email.com’
TODO: The SMTP mail server used to validate new accounts and send notifications
DISCOURSE_SMTP_ADDRESS: smtp.sparkpostmail.com # required
DISCOURSE_SMTP_PORT: 587 # (optional, default 587)
DISCOURSE_SMTP_USER_NAME: SMTP_Injection # required
DISCOURSE_SMTP_PASSWORD: 999api999password999here999 # required, WARNING the char ‘#’ in pw can cause problems!
#DISCOURSE_SMTP_ENABLE_START_TLS: true # (optional, default true)If you added the Lets Encrypt template, uncomment below to get a free SSL certificate
#LETSENCRYPT_ACCOUNT_EMAIL: name@email.com
The CDN address for this Discourse instance (configured to pull)
see https://meta.discourse.org/t/14857 for details
#DISCOURSE_CDN_URL: //discourse-cdn.example.com
The Docker container is stateless; all data is stored in /shared
volumes:
- volume:
host: /var/discourse/shared/standalone
guest: /shared - volume:
host: /var/discourse/shared/standalone/log/var-log
guest: /var/log
Plugins go here
see https://meta.discourse.org/t/19157 for details
hooks:
after_postgres:
- exec: sudo -u postgres createdb b_discourse || exit 0
- exec:
stdin: |
grant all privileges on database b_discourse to discourse;
cmd: sudo -u postgres psql b_discourse
raise_on_fail: false- exec: /bin/bash -c 'sudo -u postgres psql b_discourse <<< "alter schema public owner to discourse;"' - exec: /bin/bash -c 'sudo -u postgres psql b_discourse <<< "create extension if not exists hstore;"' - exec: /bin/bash -c 'sudo -u postgres psql b_discourse <<< "create extension if not exists pg_trgm;"' - exec: sudo -u postgres createdb c_discourse || exit 0 - exec: stdin: | grant all privileges on database c_discourse to discourse; cmd: sudo -u postgres psql c_discourse raise_on_fail: false - exec: /bin/bash -c 'sudo -u postgres psql c_discourse <<< "alter schema public owner to discourse;"' - exec: /bin/bash -c 'sudo -u postgres psql c_discourse <<< "create extension if not exists hstore;"' - exec: /bin/bash -c 'sudo -u postgres psql c_discourse <<< "create extension if not exists pg_trgm;"' - exec: sudo -u postgres createdb d_discourse || exit 0 - exec: stdin: | grant all privileges on database d_discourse to discourse; cmd: sudo -u postgres psql d_discourse raise_on_fail: false - exec: /bin/bash -c 'sudo -u postgres psql d_discourse <<< "alter schema public owner to discourse;"' - exec: /bin/bash -c 'sudo -u postgres psql d_discourse <<< "create extension if not exists hstore;"' - exec: /bin/bash -c 'sudo -u postgres psql d_discourse <<< "create extension if not exists pg_trgm;"' - exec: sudo -u postgres createdb e_discourse || exit 0 - exec: stdin: | grant all privileges on database e_discourse to discourse; cmd: sudo -u postgres psql e_discourse raise_on_fail: false - exec: /bin/bash -c 'sudo -u postgres psql e_discourse <<< "alter schema public owner to discourse;"' - exec: /bin/bash -c 'sudo -u postgres psql e_discourse <<< "create extension if not exists hstore;"' - exec: /bin/bash -c 'sudo -u postgres psql e_discourse <<< "create extension if not exists pg_trgm;"'
after_code:
- exec:
cd: $home/plugins
cmd:
- git clone https://github.com/discourse/docker_manager.git
before_bundle_exec:
- file:
path: $home/config/multisite.yml
contents: |
secondsite:
adapter: postgresql
database: b_discourse
pool: 25
timeout: 5000
db_id: 2
host_names:
- test2.domain.com
thirdsite:
adapter: postgresql
database: c_discourse
pool: 25
timeout: 5000
db_id: 3
host_names:
- test3.domain.com
fourthsite:
adapter: postgresql
database: d_discourse
pool: 25
timeout: 5000
db_id: 4
host_names:
- test4.domain.com
fifthsite:
adapter: postgresql
database: e_discourse
pool: 25
timeout: 5000
db_id: 5
host_names:
- test5.domain.comafter_bundle_exec:
- exec: cd /var/www/discourse && sudo -E -u discourse bundle exec rake multisite:migrateAny custom commands to run after building
run:
- exec: echo “Beginning of custom commands”
If you want to set the ‘From’ email address for your first registration, uncomment and change:
After getting the first signup email, re-comment the line. It only needs to run once.
- exec: rails r “SiteSetting.notification_email=‘noreply@domain.com’”
- exec: echo “End of custom commands”
The edited parts are:
-
only expose one port 80 and do not map it.
expose:
- “80” # http
-
Edit the DISCOURSE_HOSTNAME and add VIRTUAL_HOST, LETSENCRYPT_HOST & LETSENCRYPT_EMAIL
DISCOURSE_HOSTNAME: 'test1.domain.com’
VIRTUAL_HOST: 'test1.domain.com,test2.domain.com,test3.domain.com,test4.domain.com,test5.domain.com’
LETSENCRYPT_HOST: 'test1.domain.com,test2.domain.com,test3.domain.com,test4.domain.com,test5.domain.com’
LETSENCRYPT_EMAIL: ‘name@email.com’ -
Edit DISCOURSE_DEVELOPER_EMAILS, DISCOURSE_SMTP_ADDRESS, DISCOURSE_SMTP_PORT (applicable for SparkPost), DISCOURSE_SMTP_USER_NAME & DISCOURSE_SMTP_PASSWORD
DISCOURSE_DEVELOPER_EMAILS: ‘name@email.com’
TODO: The SMTP mail server used to validate new accounts and send notifications
DISCOURSE_SMTP_ADDRESS: smtp.sparkpostmail.com # required
DISCOURSE_SMTP_PORT: 587 # (optional, default 587)
DISCOURSE_SMTP_USER_NAME: SMTP_Injection # required
DISCOURSE_SMTP_PASSWORD: 999api999password999here999 # required, WARNING the char ‘#’ in pw can cause problems! -
Add this whole chunk of code after hooks: depending on how many sites you want. Example listed below are for 5 sites: b_discourse, c_discourse, d_discourse & e_discourse. If just two sites then b_discourse is sufficient and do not include c_discourse, d_discourse & e_discourse codes.
hooks:
after_postgres:
- exec: sudo -u postgres createdb b_discourse || exit 0
- exec:
stdin: |
grant all privileges on database b_discourse to discourse;
cmd: sudo -u postgres psql b_discourse
raise_on_fail: false- exec: /bin/bash -c 'sudo -u postgres psql b_discourse <<< "alter schema public owner to discourse;"' - exec: /bin/bash -c 'sudo -u postgres psql b_discourse <<< "create extension if not exists hstore;"' - exec: /bin/bash -c 'sudo -u postgres psql b_discourse <<< "create extension if not exists pg_trgm;"' - exec: sudo -u postgres createdb c_discourse || exit 0 - exec: stdin: | grant all privileges on database c_discourse to discourse; cmd: sudo -u postgres psql c_discourse raise_on_fail: false - exec: /bin/bash -c 'sudo -u postgres psql c_discourse <<< "alter schema public owner to discourse;"' - exec: /bin/bash -c 'sudo -u postgres psql c_discourse <<< "create extension if not exists hstore;"' - exec: /bin/bash -c 'sudo -u postgres psql c_discourse <<< "create extension if not exists pg_trgm;"' - exec: sudo -u postgres createdb d_discourse || exit 0 - exec: stdin: | grant all privileges on database d_discourse to discourse; cmd: sudo -u postgres psql d_discourse raise_on_fail: false - exec: /bin/bash -c 'sudo -u postgres psql d_discourse <<< "alter schema public owner to discourse;"' - exec: /bin/bash -c 'sudo -u postgres psql d_discourse <<< "create extension if not exists hstore;"' - exec: /bin/bash -c 'sudo -u postgres psql d_discourse <<< "create extension if not exists pg_trgm;"' - exec: sudo -u postgres createdb e_discourse || exit 0 - exec: stdin: | grant all privileges on database e_discourse to discourse; cmd: sudo -u postgres psql e_discourse raise_on_fail: false - exec: /bin/bash -c 'sudo -u postgres psql e_discourse <<< "alter schema public owner to discourse;"' - exec: /bin/bash -c 'sudo -u postgres psql e_discourse <<< "create extension if not exists hstore;"' - exec: /bin/bash -c 'sudo -u postgres psql e_discourse <<< "create extension if not exists pg_trgm;"'
-
Add this chunk of code after after_code: section.
before_bundle_exec:
- file:
path: $home/config/multisite.yml
contents: |
secondsite:
adapter: postgresql
database: b_discourse
pool: 25
timeout: 5000
db_id: 2
host_names:
- test2.domain.com
thirdsite:
adapter: postgresql
database: c_discourse
pool: 25
timeout: 5000
db_id: 3
host_names:
- test3.domain.com
fourthsite:
adapter: postgresql
database: d_discourse
pool: 25
timeout: 5000
db_id: 4
host_names:
- test4.domain.com
fifthsite:
adapter: postgresql
database: e_discourse
pool: 25
timeout: 5000
db_id: 5
host_names:
- test5.domain.comafter_bundle_exec:
- exec: cd /var/www/discourse && sudo -E -u discourse bundle exec rake multisite:migrate -
Edit SiteSetting.notification_email to noreply@domain.com (applicable for using SparkPost)
- exec: rails r “SiteSetting.notification_email=‘noreply@domain.com’”
-
Press CTRL + O to save edits followed by CTRL + X to exit. Enter the following command to bootstrap:
./launcher bootstrap app
-
Applicable for using SparkPost: after bootstrap successfully, you should be able to create admin account using your predefined admin email in app.yml for test1.domain.com. However, you will not be able to create admin account for other sites as they will try to send out emails from noreply@test2.domain.com, noreply@test3.domain.com, noreply@test4.domain.com & noreply@test5.domain.com but will be rejected by SparkPost due to policy_rejection.
error_code 550
raw_reason 550 5.7.1 Unconfigured Sending Domain <test2.domain.com>
To resolve this problem, add test2.domain.com, test3.domain.com, test4.domain.com & test5.domain.com to SparkPost Sending Domains and verify using DKIM records set in DNS settings of the (sub)domains. After you have verified the sending domains successfully SparkPost will still take a couple minutes to change the sending domains’ status to Ready to send.
Then you will be able to sign up an admin account using the predefined developer email address and receive the ‘Confirm your new account’ verification email from SparkPost.