Install and Run Discourse behind Nginx the Right Way, First Time!
Everyone says it is simple. It is NOT, even especially when you want to run Discourse behind a front-facing server like Nginx. After going through enough pain to get up and running a Discourse instance on time, see how to Install and Run Discourse behind Nginx the Right Way the First Time!
This tutorial makes these assumptions, so if you meet any of them, you’re good to know:
- You’re on a VPS such as a Droplet from Digital Ocean with specs
- 1 Gig or more RAM
- 1 Core CPU or more
- You’re on Ubuntu 16.04 LTS
- You know how to work your way around your VPS
- You have a
myemail@yourdomain.co
email account at Zoho already, sending emails - You have Nginx installed
- You are serving website(s) already with Nginx
- You want Discourse installed on a subdomain, such as yologhana.khophi.co
- We use yologhana.khophi.co as the example domain
If you’re looking to install Discourse on a fresh droplet, this might not be what you want. Check out the other tutorials on how to do so.
Our Itenary
This is how you might wanna approach it, which worked for me:
- Send Discourse notification emails with Zoho Mail
- This is important so that when someone signs up, they can receive an email from Discourse to verify their account.
- Front-facing server is Nginx (Discourse uses Nginx too in its Docker instance)
- Install Docker Engine
- Install Discourse
So, without much ado, let’s get our discourse running, beginning with the simple steps.
Setting up Email
Probably a convention now, emails you send messages from but don’t intend to receive or reply to messages are usually named somewhere along the lines of ‘No Reply’.
So our email we’ll be sending user notifications from will be noreply@khophi.co
Using an email address with a
.
(dot) in the name failed several times i.eno.reply@khophi.co
. I couldn’t find any docs saying that ain’t allowed, and no errors were or are thrown, except I could NOT authenticate. I found this the hardway.Maybe I didn’t do something right somewhere, but with a dot never worked for me.
Adding an email account to your Zoho should be simple. I am of the assumption that you already have something like myemail@yourdomain.com
so you’re simply adding another user.
What password to use? All-numbers password didn’t work for me also. It is hard to tell at the moment, but it just didn’t work, always had the
Authentication Error
.
Email is out of the way.
A simple way to verify if your Zoho new email user is functioning, simply open a new Incognito (or something similar) tab in your browser, visit mail.zoho.com
, log in with your new email user.
We’ll want to send emails using the noreply@khophi.co
email address, so make sure you’ve opened up the POP
in the new email account.
Install Docker
Straightforward:
sudo apt install docker-engine sudo service docker start sudo docker run hello-world
The last line is to ensure and test if docker is properly installed.
Add the current user to the docker
group like so:
sudo usermod -aG docker $USER
Done with Docker!
Discourse, Baby!
Installing Discourse is a straightforward process too, except information relating to how to get it happening ‘straightforwardly’ is not easily found.
Let’s go!
sudo mkdir /var/discourse sudo git clone https://github.com/discourse/discourse_docker.git /var/discourse
Then, we need to manually prepare our app.yml
file.
We want to install Discourse as a standalone. By manually creating our app.yml
we get to tweak things a little bit.
cd /var/discourse sudo cp samples/standalone.yml containers/app.yml sudo nano containers/app.yml
The app.yml
gives us a lot of options, however, as you go through, notice what we comment out, such as:
- We don’t want Rate Limiting with our Discourse Standalone
- SSL will be handled by the outer Nginx, so we also disable that
- The Discourse instance is exposed on port
25654:80
on which our outer Nginx will be mapped against. The idea is so that we can share Discourse with another web server like Apache or Nginx which don’t joke with their port 80.- On my Droplet, I only have 3 ports open, including port 80, through which EVERY web request incoming goes through. That port is so crucial I can’t just kick my outer Nginx off that and replace with Discourse. That’s where what port to expose Discourse instance on is crucially useful!
In the app.yml
that shows on screen, we go like this:
I don’t just get it. Why is the configuration file not a JSON object instead?
Obviously, YAML has issues and isn’t intuitive to the average dumb guy like myself. A clear sign Discourse has been rigged for only the Elite in the society.
## 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.socketed.template.yml" #- "templates/web.ratelimited.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: - "25654:80" # - "80:80" # http # - "443:443" # https 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: "128MB" ## 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-8 # DISCOURSE_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: 2 ## TODO: The domain name this Discourse instance will respond to ## Consider this as what transforms into the server_name in an Nginx configuration DISCOURSE_HOSTNAME: 'yologhana.khophi.co' ## 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' ## This email is what you'll use to log into Discourse instance the first time. DISCOURSE_DEVELOPER_EMAILS: 'hello@khophi.co' ## TODO: The SMTP mail server used to validate new accounts and send notifications DISCOURSE_SMTP_ADDRESS: smtp.zoho.com DISCOURSE_SMTP_PORT: 587 DISCOURSE_SMTP_USER_NAME: noreply@khophi.co DISCOURSE_SMTP_PASSWORD: addpasswordhere # WARNING a char '#' in pw can cause problems! ## There wouldn't be any issue like above with password if this is a JSON object ## 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_code: - exec: cd: $home/plugins cmd: - git clone https://github.com/discourse/docker_manager.git ## Any 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@khophi.co'" - exec: echo "End of custom commands"
The above is the complete app.yml
I use, just replace the dummy parts with your own. Remember from above what you need to put in the password and username fields. The email should be without dots and the password not entirely numeric.
On a 1 Gig RAM Droplet, you might consider doing these tunings as per recommendations from Digital Ocean.
In the env section of the configuration file, set db_shared_buffers
to 128MB and UNICORN_WORKERS
to 2 so you have more memory room, like so
db_shared_buffers: "128MB"
and UNICORN_WORKERS: 2
Tuning these memory settings will optimize Discourse performance on a 1 GB Droplet.
In the run
block we’ve enabled an exec function to run. This is important to get emails sending for the first time. We can disable and rebuild later on, but for the first time, keep that line uncommented.
If you have that configuration tweaked, checked and rechecked, move onto the next part which deals with bringing up our app
container.
We disabled the "templates/web.ratelimited.template.yml"
because, our Inner Nginx tends to think only one IP address is trying to make multiple connections, which isn’t true.
See, when the Outer Nginx takes the incoming request, it funnels all the request to the Inner Nginx as a single user from a single IP. This gives the inner Nginx the impression a user is trying to bombard it with recurring multiple requests.
This causes Nginx to go in a stalemate. Disabling this here, but enabling it in the outer Nginx will actually be what we’re looking for.
If our Discourse instance is accepting requests from the public, there won’t be any need to remove the rate limiting, as incoming connections will be from random multiple users which won’t trigger the flood
error you normally get.
[wp_ad_camp_1]
Bootstrapping
Our app
needs to be bootstrapped first. We’re still in the /var/discourse
directory. That can be achieved with this:
sudo ./launcher bootstrap app
That will take some time. When done, we go ahead to bring it up with:
sudo ./launcher start app
Configure Nginx
This configuration is for the outer Nginx. We saw how to manage the inner Nginx a little bit in the previous section.
With that removal, the change applied to inner Nginx configuration when the app was built.
I use the express ‘Inner Nginx’ to refer to the Nginx bundled with Discourse’s Docker image. That Nginx is different from the ‘Outer Nginx’, the one currently facing the world.
This part, we’ll focus on the Outer Nginx configuration.
In the /etc/nginx/sites-enabled/
you should see a discourse.conf
file. If not available there, check the /etc/nginx/sites-available/
directory for the file.
If you still don’t see any, create a file, namely discourse.conf
in the /etc/nginx/sites-available/
folder and update it with these content.
server { listen 80; listen [::]:80; server_name discourse.yourdomain.co; return 301 https://$host$request_uri; } server { listen 443 ssl http2; server_name discourse.yourdomain.co; include /etc/nginx/ssl/globalssl.conf; location / { proxy_pass http://discourse.yourdomain.co:25654/; proxy_set_header Host $http_host; proxy_http_version 1.1; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_redirect http://discourse.yourdomain.co:25654/ https://discourse.yourdomain.co; } }
The above configuration uses SSL generated by LetsEncrypt. We’re in 2016, and if you don’t happen to have your sites running on HTTPS, then ask yourself why, because SSL is free and extremely easy to generate these days.
Generating an SSL for your site is out of the scope of this article, but there are plenty of articles out there to this effect.
You need the proxy_set_header X-Forwarded-Proto $scheme;
to ensure your Sidekiq works properly. Without this, you’ll come across the Forbidden
when you try to issue actions in your Sidekiq dashboard
Now that our outer Nginx too is properly setup, a restart will be needed after we check our configurations for errors, so:
sudo nginx -t
If any errors, fix them. If all goes well,
sudo service nginx restart
Try accessing discourse.yourdomain.co
in your browser and you should be greeted with Discourse with the opportunity to log in with your account.
If you can’t log into your account, you might wanna reset it. Follow the steps below:
Run this command from console:
rake admin:create
You will be asked for Email, enter the email of an existing account.
Now you will be asked: User with this email already exists! Do you want to reset the password for this email? (Y/n). Press enter to continue.
Provide the new password and confirm your password.
If it worked, you’ll see Account updated successfully!
Now switch back to the browser and log in again. There you go!