Skip to content

Instantly share code, notes, and snippets.

@b-ggs
Last active June 27, 2018 18:31
Show Gist options
  • Save b-ggs/4494c221a59e21d5dd30fc0180e2cdd9 to your computer and use it in GitHub Desktop.
Save b-ggs/4494c221a59e21d5dd30fc0180e2cdd9 to your computer and use it in GitHub Desktop.

Rails 5.2 + Ubuntu Bionic + Passenger + Chruby + MySQL Production Deployment

Create and set up a new user

Create user deploy and add it to the sudoers

sudo adduser deploy
sudo adduser deploy sudo

Login as deploy

su deploy

Generate SSH keys for deploy

ssh-keygen

Copy authorized_keys from root to deploy

sudo cp /root/.ssh/authorized_keys ~/.ssh/
sudo chown deploy ~/.ssh/authorized_keys

Disable password authentication for root and deploy

sudo vi /etc/ssh/sshd_config

Ensure that the following are set:

ChallengeResponseAuthentication no
PasswordAuthentication no
UsePAM no
PermitRootLogin no

Restart the SSH service

sudo service ssh restart

Install Ruby via ruby-install

Source: https://github.com/postmodern/ruby-install

Install dependencies from APT

sudo apt update
sudo apt install -y build-essential

Install ruby-install from source

mkdir -p ~/packages && cd ~/packages
wget -O ruby-install-0.6.1.tar.gz https://github.com/postmodern/ruby-install/archive/v0.6.1.tar.gz
tar -xzvf ruby-install-0.6.1.tar.gz
cd ruby-install-0.6.1/
sudo make install

Install Ruby via ruby-install

ruby-install ruby 2.5.1

Install chruby

Source: https://github.com/postmodern/chruby/

Install chruby from source

mkdir -p ~/packages && cd ~/packages
wget -O chruby-0.3.9.tar.gz https://github.com/postmodern/chruby/archive/v0.3.9.tar.gz
tar -xzvf chruby-0.3.9.tar.gz
cd chruby-0.3.9/
sudo make install

Enable chruby system-wide

sudo vi /etc/profile.d/chruby.sh
if [ -n "$BASH_VERSION" ] || [ -n "$ZSH_VERSION" ]; then
  source /usr/local/share/chruby/chruby.sh
  source /usr/local/share/chruby/auto.sh
fi

Install bundler

chruby 2.5.1
gem install bundler

Install Passenger

Source: https://www.phusionpassenger.com/library/install/nginx/install/oss/bionic/

Install nginx from APT

sudo apt install -y nginx

Follow Phusion's guide to setting up Passenger on Ubuntu Bionic

Install Passenger packages

sudo apt-get install -y dirmngr gnupg
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 561F9B9CAC40B2F7
sudo apt-get install -y apt-transport-https ca-certificates

sudo sh -c 'echo deb https://oss-binaries.phusionpassenger.com/apt/passenger bionic main > /etc/apt/sources.list.d/passenger.list'
sudo apt-get update

sudo apt-get install -y libnginx-mod-http-passenger

Enable Passenger nginx module

if [ ! -f /etc/nginx/modules-enabled/50-mod-http-passenger.conf ]; then sudo ln -s /usr/share/nginx/modules-available/mod-http-passenger.load /etc/nginx/modules-enabled/50-mod-http-passenger.conf ; fi
sudo ls /etc/nginx/conf.d/mod-http-passenger.conf

Restart nginx

sudo service nginx restart

Validate Passenger installation

sudo /usr/bin/passenger-config validate-install

Set up Passenger to use chruby

Source: https://github.com/postmodern/chruby/wiki/Passenger

Create a wrapper for Ruby

sudo vi /usr/local/bin/chruby-wrapper
#!/bin/bash

source /usr/local/share/chruby/chruby.sh
source /usr/local/share/chruby/auto.sh

chruby_auto
export GEM_HOME="$deploy_user_home/.gem/$RUBY_ENGINE/$RUBY_VERSION"
export GEM_PATH="$GEM_HOME:$GEM_PATH"
export PATH="$GEM_HOME/bin:$PATH"

exec "ruby" "$@"
sudo chmod +x /usr/local/bin/chruby-wrapper

Configure Passenger to use chruby-wrapper

sudo vim /etc/nginx/conf.d/mod-http-passenger.conf
passenger_ruby /usr/local/bin/chruby-wrapper;

Restart nginx

sudo service nginx restart

Install and set up MySQL

Install MySQL from APT

sudo apt install -y mysql-server mysql-client libmysqlclient-dev

Generate a secure password using your tool of choice

</dev/urandom tr -dc '1234567890!@#$%^&*()-=qwertyQWERTYasdfghjklASDFGHJKLzxcvbnmZXCVBNM' | head -c14; echo

Create and set up a new MySQL user

sudo mysql
CREATE USER '<APP_NAME>'@'localhost' IDENTIFIED BY '<YOUR_NEW_PASSWORD>';
GRANT ALL PRIVILEGES ON * . * TO '<APP_NAME>'@'localhost';
FLUSH PRIVILEGES;

Login as the new user

mysql -u <APP_NAME> -p

Create the database for your Rails app

CREATE DATABASE <APP_NAME>;

Install other dependencies from APT

sudo apt install -y nodejs

Set up your Rails app for deployment

Add Capistrano and Passenger to the Gemfile

group :production do
  # ...
  passenger
end

group :development do
  # ...
  gem 'capistrano', require: false
  gem 'capistrano-rails', require: false
  gem 'capistrano-passenger', require: false
  gem 'capistrano-chruby', require: false
end

Note: Also add mysql2 to the production group if you don't already use it in development

bundle install

Capify the Rails project

bundle exec cap STAGES=production

Note: To create additional stages: STAGES=production,staging,foobar

Configure the newly created Capistrano configs

  • Capfile
require 'capistrano/setup'
require 'capistrano/deploy'

require 'capistrano/scm/git'
install_plugin Capistrano::SCM::Git

require 'capistrano/rails'
require 'capistrano/passenger'
require 'capistrano/chruby'

Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r }
  • config/deploy.rb
# ...
set :application, '<APP_NAME>'
set :repo_url, 'git@<SERVICE>.com:<USERNAME>/<APP_NAME>.git'
set :chruby_ruby, 'ruby-2.5.1'
set :deploy_to, '/home/deploy/<APP_NAME>'
set :linked_files, %w{config/database.yml config/master.key}
  • config/deploy/production.rb
server ENV.fetch('SERVER_IP'),
  user: 'deploy',
  roles: %w{app db web},
  ssh_options: {
    keys: ENV.include?('KEYFILE') ? [ENV.fetch('KEYFILE')] : ['~/.ssh/id_rsa']
  }

Note: The check for an ENV variable named KEYFILE is for when you need to specify a different private key on your machine. You can use this by prepending KEYFILE=/path/to/your/id_rsa to bundle exec cap production deploy

Move all relevant config files to example ymls

cp config/database.yml config/database.example.yml
git rm --cached config/database.yml
  • .gitignore
# ...
/config/database.yml

Set up linked files on your production server

mkdir -p ~/<APP_NAME>/shared/config
cd ~/<APP_NAME>/shared
  • config/database.yml
production:
  adapter: mysql2
  username: <YOUR_MYSQL_USERNAME>
  password: <YOUR_MYSQL_PASSWORD>
  database: <YOUR_MYSQL_DATABASE_NAME>
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  timeout: 5000
  • config/master.key

TODO: Find out if this is the right way to do it?

Copy over the generated config/master.key from your local machine.

Perform an initial deployment

SERVER_IP=<YOUR_SERVER_IP> cap production deploy

Alternatively, you use a different keyfile for deployment:

SERVER_IP=<YOUR_SERVER_IP> KEYFILE=/path/to/your/id_rsa cap production deploy

Add nginx host

sudo vi /etc/nginx/sites-enabled/default
server {
        listen 80;
        listen [::]:80 ipv6only=on;

        server_name <MY_DOMAIN>.com;
        passenger_enabled on;
        rails_env    production;
        root         /home/deploy/<APP_NAME>/current/public;

        # redirect server error pages to the static page /50x.html
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
}

Restart nginx

sudo service nginx restart

SSL installation

https://medium.com/@mrkdsgn/steps-to-install-a-go-daddy-ssl-certificate-on-nginx-on-ubuntu-14-04-ff942b9fd7ff

server {
    listen 80;
    listen [::]:80;
    server_name essaysonthedot.com www.essaysonthedot.com;
    return 301 https://essaysonthedot.com$request_uri;
}

server {
        listen 443;
        # listen [::]:80 ipv6only=on;

        server_name essaysonthedot.com;
        passenger_enabled on;
        rails_env    production;
        root         /home/deploy/essaysonthedot/current/public;

        ssl on;
        ssl_certificate /etc/nginx/ssl/essaysonthedot.com.chained.crt;
        ssl_certificate_key /etc/nginx/ssl/essaysonthedot.key;
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_prefer_server_ciphers on;
        ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';

        # redirect server error pages to the static page /50x.html
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment