Callen Hedglen logo

Senior UX professional

Callen Hedglen profile photo
Email sender profile photo
Callen Hedglen
Jan 18, 2022
To: Internet Person

Creating a Discourse Q/A Site with Linode and CyberPanel

As I go through my journey of rapidly building internet projects, I’ve found myself trying to get traffic to my websites.

Where are people searching? How are they going to find MY site?

It’s a dilemma that every indie hacker faces. Some solve this problem with a giant launch on a site like HackerNews or Product Hunt. Others leverage their existing audiences from their newsletter or Twitter audience.

For most of us though, we don’t have an audience; our project is also unlikely to rank highly on HackerNews. So what can we do?

We do what internet gurus have been doing since the early days of the internet: focus on SEO the old-fashioned way.

It’s not glamorous, takes a lot of work, and this process won’t provide instant gratification. But! Your effort will compound, and it’s a free way to get traffic to your site.

With this in mind, you may have noticed Google has been giving extra attention to content that’s formatted in Q/A format.

People are searching questions all day long, and the content best formatted to answer these questions gets ranked at the top.

So, let’s create a Q/A site on the cheap, so that your project can start ranking in Google!

Before we get started:

  • Do you have a Linode VPS up and running with a CyberPanel control panel? If not, I’ll be releasing an article on how to do this soon.
  • Do you have a way to send emails? I send mine using MXRoute, and will be releasing an article on how to do this soon.

Our High-Level Plan

Install a Q/A site on a subdomain. For this tutorial, using community.labeltap.com as my subdomain and hello@labeltap.com as my email.

Tools Used

  • Linode VPS
    How our Q/A site will be on the internet
  • CyberPanel
    How we edit files and manage settings easily
  • Terminal
    For the things we can’t do within CyerPanel
  • MXRoute
    For sending emails
  • Discourse
    The easily adaptable, opensource Q/A platform

Step 1: Setup Email

This is a requirement of Discourse. Follow my instructions on setting up an email address in MXRoute.

Step 2: Activate Docker in CyberPanel

Follow this guide to install Docker on your VPS

This will allow us to get Discourse up and running.

Make sure you are on at least version 2.1.2 (the latest as of this writing) of CyberPanel. I was on an older version, and I had to update to 2.1.2 to get a docker bug fixed. If you need to update, follow these instructions.

Step 3: Create Your Website in CyberPanel

You’ll need to create the website where your Q/A site will live. You can do this by going to Websites >> Create website

I selected PHP 8.0 and only added SSL as an additional feature.

Step 4: Make Your Directories

You can do this within the terminal, but I just prefer doing things visually.

In CyberPanel, go to Websites >> List Websites

On the website you just created, click File Manager

Double click on public_html

Add a folder called var and click into that folder

Add a folder called discourse

Step 5: Clone Discourse

Now that we have a spot to install it, it’s time to get discourse on our site.

Open the terminal, and SSH into your linode. If you click on your linode in the linode dashboard, you’ll be able to copy the command for this. It will be something like:

ssh root@12.34.567.890

Pro tip: you can use the up arrow in the future to get to your recent commands

Now that you’re SSH’d in, enter this command, but with your info put in it (make sure there's a space inbetween the github url and your directory path).

git clone https://github.com/discourse/discourse_docker.git /home//public_html/var/discourse

Step 6: Adjust Your Discourse Settings

Now that we’ve cloned Discourse, we need to update a few things to make it work for us.

First, navigate to the directory we created earlier:

cd /var/discourse

And now duplicate Discourse’s sample YML file:

cp samples/standalone.yml containers/app.yml

Go into CyberPanel’s File Manager we were in earlier, and find the app.yml file we just created (in var/discourse/containers). Right click, and Edit With CodeMirror

This file contains the settings used to create your version of Discourse. You’ll need to replace all of this text with the updated version I modified below:

## 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: 'hello@domain.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'
  ## This email is what you'll use to log into Discourse instance the first time.
  DISCOURSE_DEVELOPER_EMAILS: 'hello@domain.com'

  ## TODO: The SMTP mail server used to validate new accounts and send notifications
  DISCOURSE_SMTP_ADDRESS: whatever.mxroute.net
  DISCOURSE_SMTP_PORT: 587
  DISCOURSE_SMTP_USER_NAME: hello@domain.com
  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
- 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='hello@labeltap.com'" - exec: echo "End of custom commands"

 

There’s a couple fields you’ll need to update:
25654:80

You don’t need to update this for your first install, but this port number is important, and how we’re able to run multiple versions of Discourse on Linode. For future installs, each one will have to use a different port number that’s not already in use.

DISCOURSE_HOSTNAME
DISCOURSE_DEVELOPER_EMAILS
DISCOURSE_SMTP_ADDRESS

This is the what your email service uses to send email (mine was assigned to me by MXRoute). You’ll have to verify your email to install Discourse, so you do have to have email setup before running this.

DISCOURSE_SMTP_USER_NAME
DISCOURSE_SMTP_PASSWORD

Update these settings, and click “Save”.

Step 7: Launch Discourse

Make sure you’re still in the /var/discourse/ directory.

If you are, run the command: 

./launcher bootstrap app

This will get everything setup for the first time! It will take a few minutes.

When you’re done, run the command:

./launcher start app

Step 8: Setup Reverse Proxy

Make sure your still SSH’d into your Linode, and get to your root directory by typing:

cd

Now that we’re in the root directory, run this command:

vim /usr/local/lsws/conf/httpd_config.conf

Use your arrows to go to the bottom of the document. Click “i” on your keyboard to edit.

Copy and paste this text to put at the bottom of the file, making sure that the port number is the one you used earlier:

extprocessor dockerbackend {
type                    proxy
address                 127.0.0.1:25654
maxConns                100
pcKeepAliveTimeout      60
initTimeout             60
retryTimeout            0
respBuffer              0
}

Hit ESC on your keyboard, and then :wq to save and quit.

Step 9: Configure Rewrite Settings

Last setting! Go to Websites >> List Websites and click Manage on your website. From there, you should find a link that says Rewrite Rules.

Paste this text in there:

RewriteCond %{HTTPS} !=on
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
REWRITERULE ^(.*)$ http://dockerbackend/$1 [P]

After saving your rewrite rules, you should be able to view your Discourse install online at your subdomain! From there, it’s a matter of setting up the Q/A site to the settings you need.

About Callen
Hello! I'm a grizzled veteran of a UX designer with a specialization in e-commerce. I'm also an aspiring indie hacker, with a goal of launching 12 products in 12 months during 2022.
Follow me for UX tips and updates on my #BuildInPublic journey:
This entire site was created using PHP, Tailwind CSS, and lives on a Linode VPS.