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.
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.