September 18, 2016

Manually setup a Ruby on Rails server on Ubuntu

When you have built your rails application, and your are ready to get it on the internet you basically have to decide, do you want to use Heroku, or do you want to use a server that you control, but also have to setup and maintain. If you do want to do that, then you are going to need to set it up to run your Ruby application, and this post will take you through how to do that manually on Ubuntu.

Choose a provider

First you need to go and get a Virtual Private Server, VPS, that you can run your application on. EitherDigitial Ocean or Linode are excellent choices, made for developers, I can recommend each of them from previous use, and have my referral codes in those links for if you are so inclined. Once you have a VPS with a provider that you are happy with then you are ready to proceed further. For a rails app you probably want to choose a server with at least 2GB of RAM, but 1 may well be fine if you don't have to run any background jobs, or can get away with just running a couple of passenger instances.

Getting started

So first up you are going to have to get onto your new server. So from your terminal you need to ssh in

It will ask for a password that you either setup or was assigned to you when you setup your server with your provider, enter it and you will be on your server ready to get started.

Add a User

So first order of business is adding an unprivileged user to run the application and the deployments as it it not a good idea or considered good practise to be using the root user of the system for these things. So first up let's do that:

# adduser deploy

Once you have filled out the information that is asked for, you need to add this user to the sudoers file so that this user can perform privileged actions, then it can be used to setup the server rather than the root user. To do this:

vi /etc/sudoers

Then add this line to the file, either in the Users section if one is specified, or at the end will be fine:

root    ALL=(ALL:ALL) ALL

Now we need to add an ssh public key for our deployment user so as root, let's become the deploy user:

sudo su deploy

And add an SSH public key from your machine onto the server ready to login via ssh:

mkdir ~/.ssh
chmod 0700 ~/.ssh
cd ~/.ssh
vi authorized_keys

Paste your public key into the authorized_keys file, then make sure the permissions are tight enough that ssh will use them:

chmod 0600 ~/.ssh/*

You should make sure that this key is also the key that you have as a deployment key on Github so your server can access your repository to checkout code when you deploy.

Lock down SSH

The last step before logging back in as the deploy user is to setup SSH to not allow root login, and to allow login via public key only. This way no one can get direct access to your server as the root user, and also without your public key. So it's necessary to change the PermitRootLogin setting in the config file:

vi /etc/ssh/sshd_config

And make this change:

PermitRootLogin No

And at the end of the file add:

PasswordAuthentication no

so that no one can login into the server without having the required public key. Now it's time to restart ssh:

service ssh restart

and then logout and log back in as the deploy user, ready to install the software.

Install the Software

It's time now to install the software that you need to run your Ruby on Rails application, so we need to update apt, ubuntu/debian package software, then install the dependencies we need to install Ruby:

sudo apt-get update
sudo apt-get install build-essential libssl-dev libyaml-dev libreadline-dev openssl curl git-core zlib1g-dev bison libxml2-dev libxslt1-dev libcurl4-openssl-dev nodejs libsqlite3-dev sqlite3
Rbenv and Ruby

With the dependencies installed it is time to instsall ruby. A great way to do this is with rbenv, you don't have to wait for system package managers to update with new rubies, they are often behind, so with rbenv it will allow you to be able to install new Ruby versions to stay up to date with security patches swiftly. Let's install it:

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

It's also required to install the ruby-build plugin, this will need updating whenever there is a new ruby version to be installed:

git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
echo 'export PATH="$HOME/.rbenv/plugins/ruby-build/bin:$PATH"' >> ~/.bashrc
exec $SHELL

And then finally time to install Ruby, and make it the global version for this user on the system:

rbenv install 2.3.1
rbenv global 2.3.1
ruby -v

Lastly, install bundler:

gem install bundler

Then because it's rbenv it's required to run rehash to make the binaries available to the shell:

rbenv rehash
Install Nginx with passenger

Nginx is the webserver software that I would recommend that you use for serving your application and web requests. By using Phusion Passenger you can cut down one extra moving part of your application as opposed to using something like Unicorn, or Puma, and Nginx as opposed to Apache due to it's smaller memory footprint which is valuable in low memory setups to run small apps via a VPS. With Nginx though, you can't add Passenger to a compiled and running server like you can with Apache, but luckily, Phusion has released an official Ubuntu package that has Passenger built into Nginx and ready to run, so that is what needs to be installed:

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

# Add Passenger APT repository
sudo sh -c 'echo deb https://oss-binaries.phusionpassenger.com/apt/passenger xenial main > /etc/apt/sources.list.d/passenger.list'
sudo apt-get update

# Install Passenger & Nginx
sudo apt-get install -y nginx-extras passenger

Once this is installed nginx is ready to run, but passenger isn't quite. Open up the nginx config file:

sudo vi /etc/nginx/nginx.conf

and uncomment some passenger settings in the config so Nginx knows what it needs to know to run your ruby:

##
# Phusion Passenger
##
# Uncomment it if you installed ruby-passenger or ruby-passenger-enterprise
##

passenger_root /usr/lib/ruby/vendor_ruby/phusion_passenger/locations.ini;
passenger_ruby /home/deploy/.rbenv/shims/ruby;

# Add this
passenger_max_pool_size 4;

Now let's install the vhost that will run the application:

sudo vi /etc/nginx/sites-available/default

and edit the file to contain:

server {
  listen 80 default_server;
  server_name SERVER_NAME_HERE; # You'll need your domain name here
  passenger_enabled on;
  passenger_app_env production;
  passenger_min_instances 1;
  root /path/to/application/current/public; # This is where the app will be deployed

  location ~* \.(svg|ico|css|js|gif|jpe?g|png)(\?[0-9]+)?$ {
    gzip_static on;
    expires max;
    add_header Cache-Control public;
    break;
  }

  error_page 500 502 503 504 /500.html;
  location = /500.html {
    root /path/to/application/current/public;
  }

}

and then it's time to start nginx:

sudo service nginx start
Install Database - Postgresql

The database of choice for running your rails app is postgresql, let's get that installed to run the app:

sudo apt-get install postgresql postgresql-contrib libpq-dev

And add a user to run the database, this will be the user to be used in the production settings in the application database.yml:

sudo su - postgres
createuser --pwprompt
exit
Put up a firewall

Lastly, lock down the server so that there is as few entry points as possible as possible vectors for attack, so the only ports open should be for ssh and http, so first up set the default policy to deny connections:

sudo ufw default deny

and then open up the ports for ssh and http:

sudo ufw allow 80
sudo ufw allow ssh

And if you are accepting secure connections to nginx:

sudo ufw allow 443
Final Thoughts

This is the basic setup of a VPS to run a Ruby on Rails application. It's a lot to do and remember, and it's unfortunately not very scalable or repeateable to just go and type on the server each time you want to setup a new server for a new or existing application. It is far preferable to have an automated way to setup servers to run your applications to avoid this lengthy process. If you want to be able to setup a server with two commands via Ansible though, sign up to my mailing list below and I'll send you an Ansible playbook built specifically to setup a Ruby on Rails application server like this one, complete with Letsencrypt SSL certificates for your application. If you'd rather stick witht he manual method, I recommend you go and install SSL with Letsencrypt.