Update 2020-05-10: This tutorial is based on old version Discourse and Mina. Not sure if this tutorial is still working.

First of all, you should follow official Docker-based installation guide to deploy your Discourse. I use mina to deploy my Discourse to my VPS because it’s easier and faster for me.

Preparations

  1. I follow Discourse Advanced Developer Install Guide and make sure Discourse can run on localhost. My Discourse version at the time is 1.0.0beta4.

  2. Buy VPS with specifications:

    • 2 CPU @2,4 GHz
    • 1 GB RAM
    • 20 GB HDD
    • 256 Swap Memory
    • Ubuntu 14.04

Setup VPS

first of all, login as root before run the commands below. stop unused service and create new standard user.

sudo service apache2 stop
sudo service mysql stop
adduser myexampleuser

Install PostgreSQL database.

sh -c "echo 'deb http://apt.postgresql.org/pub/repos/apt/ precise-pgdg main' > /etc/apt/sources.list.d/pgdg.list"
wget --quiet -O - http://apt.postgresql.org/pub/repos/apt/ACCC4CF8.asc | sudo apt-key add -
wget --quiet -O - http://apt.postgresql.org/pub/repos/apt/ACCC4CF8.asc | apt-key add -
apt-get update
apt-get install postgresql-common
apt-get install postgresql-9.3 libpq-dev postgresql-contrib-9.3

Set PostgreSQL language.

export LANG="en_US.UTF-8"
service postgresql restart

Edit /etc/postgresql/9.3/main/pg_hba.conf, and find the line below.

local   all   all   peer

Change to this line below.

local   all   all   md5

Restart PostgreSQL and Create database with username and password.

sudo service postgresql restart
sudo -u postgres createuser mydatabaseuser -s -P

Recreate template1 to use UTF-8 based on these instructions.

UPDATE pg_database SET datistemplate = FALSE WHERE datname = 'template1';
DROP DATABASE template1;
CREATE DATABASE template1 WITH TEMPLATE = template0 ENCODING = 'UNICODE';
UPDATE pg_database SET datistemplate = TRUE WHERE datname = 'template1';
\c template1
VACUUM FREEZE;
UPDATE pg_database SET datallowconn = FALSE WHERE datname = 'template1'

Create Discourse database.

createdb -U mydatabaseuser discourse_production

Install Nginx, NodeJS, NPM and Redis.

apt-get install nginx
apt-get install nodejs
apt-get install npm
apt-get install redis-server

// run command below to start redis
sudo service redis-server start

Install ImageMagick and image optimizer.

apt-get install ImageMagick
apt-get install libxml2 libpq-dev g++
apt-get install pngquant
apt-get install jhead
apt-get install jpegoptim
apt-get install jpegtran
apt-get install libjpeg-progs
apt-get install gifsicle

ln -s /usr/bin/nodejs /usr/bin/node
npm install -g svgo

Install Rbenv and Ruby dependencies, taken from Digital Ocean article.

apt-get install git-core curl zlib1g-dev build-essential libssl-dev libreadline-dev libyaml-dev libsqlite3-dev sqlite3 libxml2-dev libxslt1-dev libcurl4-openssl-dev python-software-properties libffi-dev

Setup Rbenv.

cd
git clone git://github.com/sstephenson/rbenv.git .rbenv
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
echo 'eval "$(rbenv init -)"' >> ~/.bash_profile

git clone git://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
echo 'export PATH="$HOME/.rbenv/plugins/ruby-build/bin:$PATH"' >> ~/.bash_profile
source ~/.bash_profile

Setup ssh for deployment with Mina.

ssh [email protected]
mkdir -p apps/discourse
ssh-keyscan -H github.com >> ~/.ssh/known_hosts

Done, exit from VPS.

Setup Mina The Deployer

Mina is really fast deployer and server automation tool. Mina only creates one SSH session per deploy, minimizing the SSH connection overhead. Here is the steps to setup Mina The Deployer.

Clone Discourse.

git clone [email protected]:discourse/discourse.git
cd discourse

Add mina to Gemfile.

# Gemfile
gem 'mina'

Generate mina config file inside Discource directory.

mina init

Configure Thin, Sidekiq, and Mina. I’ve experiences my VPS out of memory multiple times. To prevent that happen again, I set RUBY_GC_MALLOC_LIMIT=25000000, 2 thin server, and 5 sidekiq workers. Here are my complete mina, thin, and sidekiq configuration.

Thin web server configuration.

# config/thin.yml
chdir: /home/myexampleuser/apps/discourse/current
environment: production
pid: /home/myexampleuser/apps/discourse/shared/pids/thin.pid
socket: /home/myexampleuser/apps/discourse/tmp/thin.sock
servers: 2
daemonize: true
onebyone: true

Sidekiq configuration.

# config/sidekiq.yml
production:
  :concurrency: 5
  :daemon: true
  :logfile: /home/myexampleuser/apps/discourse/shared/log/sidekiq.log
  :pidfile: /home/myexampleuser/apps/discourse/tmp/pids/sidekiq.pid

Mina configuration (Gist)

# config/deploy.rb

require 'mina/bundler'
require 'mina/rails'
require 'mina/git'
require 'mina/rbenv'

set :term_mode, nil
set :rails_env, 'production'

set :domain, 'example.com'
set :deploy_to, '/home/myexampleuser/apps/discourse'
set :repository, '[email protected]:discourse/discourse.git'
set :branch, 'master'

set :shared_paths, ['config/discourse.conf', 'log', 'public/uploads', 'public/backups', 'tmp/pids']
set :user, 'myexampleuser'    # Username in the server to SSH to.
set :forward_agent, true     # SSH forward_agent.

task :environment do

task :setup => :environment do
  queue! %[mkdir -p "#{deploy_to}/#{shared_path}/log"]
  queue! %[chmod g+rx,u+rwx "#{deploy_to}/#{shared_path}/log"]

  queue! %[mkdir -p "#{deploy_to}/#{shared_path}/config"]
  queue! %[chmod g+rx,u+rwx "#{deploy_to}/#{shared_path}/config"]
  queue! %[touch "#{deploy_to}/#{shared_path}/config/discourse.conf"]

  queue! %[mkdir -p "#{deploy_to}/#{shared_path}/public/uploads"]
  queue! %[mkdir -p "#{deploy_to}/#{shared_path}/public/backups"]
  queue! %[chmod g+rx,u+rwx "#{deploy_to}/#{shared_path}/public/uploads"]
  queue! %[chmod g+rx,u+rwx "#{deploy_to}/#{shared_path}/public/backups"]

  queue! %[mkdir -p "#{deploy_to}/tmp/pids"]
  queue! %[chmod g+rx,u+rwx "#{deploy_to}/tmp/pids"]
end

desc "Deploys the current version to the server."
task :deploy => :environment do
  deploy do
    invoke :'git:clone'
    invoke :'deploy:link_shared_paths'
    invoke :'bundle:install'
    invoke :'rails:db_migrate'
    invoke :'rails:assets_precompile'
    invoke :'deploy:cleanup'

    to :launch do
      # uncomment if you want restart thin and on deploy
      #invoke :'sidekiq:restart'
      #invoke :'thin:restart'
    end
  end
end

namespace :thin do
  desc "start thin server"
  task :start => :environment do
    queue "cd #{deploy_to}/#{current_path}/ && RUBY_GC_MALLOC_LIMIT=25000000 bundle exec thin -C config/thin.yml start"
  end

  desc "stop thin server"
  task :stop => :environment do
    queue "cd #{deploy_to}/#{current_path}/ && RUBY_GC_MALLOC_LIMIT=25000000 bundle exec thin -C config/thin.yml stop"
  end

  desc "restart thin server"
  task :restart => :environment do
    queue "cd #{deploy_to}/#{current_path}/ && RUBY_GC_MALLOC_LIMIT=25000000 bundle exec thin -C config/thin.yml restart"
  end
end

namespace :sidekiq do
  desc "start sidekiq"
  task :start => :environment do
    queue "cd #{deploy_to}/#{current_path}/ && bundle exec sidekiq -C config/sidekiq.yml -e production"
  end

  desc "stop sidekiq"
  task :stop => :environment do
    queue "cd #{deploy_to}/#{current_path}/ && bundle exec sidekiqctl stop /home/myexampleuser/apps/discourse/tmp/pids/sidekiq.pid"
  end

  desc "restart sidekiq"
  task :restart => :environment do
    invoke :'sidekiq:stop'
    invoke :'sidekiq:start'
  end

end

Run Mina Setup and deploy Command.

mina setup
mina deploy

login to server and copy content from config/discourse_defaults.conf to ~/apps/discourse/shared/config/discourse.conf and replace with these configuration.

db_name = discourse_production
db_username = mydatabaseuser
db_password = mydatabasepassword
hostname = forum.example.com

login as root and copy Nginx configuration.

cd ~/apps/discourse/current
cp config/nginx.global.conf /etc/nginx/conf.d/
cp config/nginx.sample.conf /etc/nginx/conf.d/discourse.conf

Configure Nginx. Open /etc/nginx/conf.d/discourse.conf and replace with these configuration.

# Only use 2 Thin server
upstream discourse {
  server unix:/home/myexampleuser/apps/discourse/tmp/thin.0.sock;
  server unix:/home/myexampleuser/apps/discourse/tmp/thin.1.sock;
}

server_name forum.example.com;

Restart Nginx web server.

sudo service nginx restart

Exit from server, start Sidekiq and Thin

mina sidekiq:start
mina thin:start

Done! :)