Deploying a Rails app to AWS with Passenger, Nginx and Capistrano for the first time.

So this week i was looking around for a good tutorial on how to deploy a rails app to AWS with Capistrano (i’d never used Capistrano before). I found some, of course, but none that quite fit my interpretation of good — verbose yet concise (i know that’s a bit of an oxymoron). After reading a few of these tutorials i eventually figured out how to do what i wanted and because i couldn’t find that one ‘good’ tutorial, this is my attempt to explain it myself.

So i’m deploying to an AWS (amazon web services) EC2 instance (Ubuntu — trusty 14.04) and my stack is Nginx + Passenger + Sqlite3.

Nginx = Web server

Passenger = Application server

Sqlite3 = Database

Here’s a good explanation of the difference between a Web Server and an App Server http://www.justinweiss.com/articles/a-web-server-vs-an-app-server/

Your App

I’m going to assume that you have your own rails app to deploy — doesn’t have to be anything special, just something that works and you can play around with.

For ease, i’m just using the default sqlite3 database that comes with rails. So something like this:

default: &default
adapter: sqlite3
pool: 5
timeout: 5000
production:
<<: *default
database: db/production.sqlite3

Setup Capistrano

Let’s add Capistrano to the app. In the gemfile, add the following and run bundle install:

gem ‘capistrano’
gem ‘capistrano-rails’
gem ‘capistrano-bundler'

Next we should configure Capistrano by first using the following command to generate the config files:

cap install STAGES=production

This creates three files: Capfile, config/deploy.rb and config/deploy/production.rb

In the Capfile uncomment these lines:

require ‘capistrano/bundler’
require ‘capistrano/rails/assets’
require ‘capistrano/rails/migrations’

In the config/deploy.rb ensure you have the following:

set :application, ‘my-app-name’ #change this to the name of your app
set :repo_url, 'git@github.com:username/my-app-name.git' #change this to the url of your app on github
set :deploy_to, '/var/www/my-app-name'
set :use_sudo, true
set :branch, 'master' #or whichever branch you want to use
set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', 'public/system')

We still have to edit the config/deploy/production.rb file but we’ll leave that until after we have the server running.

Setup AWS

Add key pair to AWS so we can ssh into the server later — I like to import my local machine’s existing public key to AWS.

In local machine terminal enter the following command and copy the result.

cat ~/.ssh/id_rsa.pub

From the AWS EC2 dashboard click ‘Key Pairs’, then ‘Import Key Pairs’. Name the key and paste the key you just copied from your local machine. Then click ‘Import’.

When launching your EC2 instance using the wizard make sure your AMI is Ubuntu Server 14.04 LTS (HVM), SSD Volume Type and your instance type is t2.micro. On the configure instance page make sure that the Auto-assign Public IP option is set to ‘Enable’. Don’t bother with setting up a database or anything and make sure that the security group for your instance has a rule that allows inbound HTTP requests. After you click launch on the Review page, you will be asked about your key pair. Use the one you imported earlier.

Enable Capistrano to get app repo from Github and deploy

Now that we’ve launched the server instance, copy its public IP and open up the config/deploy/production.rb file we generated earlier on your local machine. Edit it to resemble something like this. Change the IP to the one you copied.

server ‘52.16.139.61’, user: ‘ubuntu’, roles: %w{web app db}
set :ssh_options, { forward_agent: true }

The second line will allow the server to connect to Github using the local machine’s key. In order to make it work though we need to make a couple of changes on the local machine. Open the file ~/.ssh/config and add the following making sure to replace the ip address with yours or that of your domain.

Host 52.16.139.61
ForwardAgent yes

Next, you need to add your local private key to the ssh-agent.

ssh-add ~/.ssh/id_rsa

Configure the server

Now it’s time to do some stuff on the server. Lets SSH into the server:

ssh -i ~/.ssh/id_rsa ubuntu@52.16.139.61

Create the directory that the app will live in:

mkdir -p /var/www

Update all existing packages on the server:

sudo apt-get update && sudo apt-get -y upgrade

Install Ruby from source (remember to change to the version of ruby you’re using):

cd
wget http://ftp.ruby-lang.org/pub/ruby/2.2/ruby-2.2.3.tar.gz
tar -xzvf ruby-2.2.3.tar.gz
cd ruby-2.2.3/
./configure
make
sudo make install
ruby -v

Prevent gem docs from being installed going forward and then install bundler:

echo "gem: --no-ri --no-rdoc" > ~/.gemrc
gem install bundler

Install git on the server:

sudo apt-get install git

Install Passenger + Nginx:

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 trusty main > /etc/apt/sources.list.d/passenger.list'
sudo apt-get install -y nginx-extras passenger

Enable the Passenger Nginx module by opening /etc/nginx/nginx.conf and uncommenting the following lines:

# passenger_root /some-filename/locations.ini;
# passenger_ruby /usr/bin/passenger_free_ruby;

Restart Nginx and validate the installation:

sudo service nginx restart
sudo passenger-config validate-install

What we should do now is make sure that Nginx knows where our application code is after we deploy. Create a new conf file in this directory /etc/nginx/sites-enabled with the same name as your app:

cd /etc/nginx/sites-enabled
touch my-app.conf

Let’s open that file and add the following:

server {
listen 80;
server_name 52.16.139.61; #replace with your server ip or domain
# Tell Nginx and Passenger where your app’s ‘public’ directory is
root /var/www/my-app/current/public;
# Turn on Passenger
passenger_enabled on;
passenger_ruby /usr/local/bin/ruby;
}

The last line ‘passenger_ruby’ is where we give Passenger the path to the correct Ruby interpreter to use. If you don’t know it or the one above is incorrect, here’s how to get it:

passenger-config about ruby-command

Now we should restart Nginx again:

sudo service nginx restart

Deploy the app with Capistrano

Ensure that the below two gems are in your Gemfile and bundle install. Instead of ‘therubyracer’ we could just install Node.js on the server but there’s some kind of problem with it and Passenger that we’d have to get around, so for now just do the below.

gem "therubyracer"
gem "execjs"

Once, the latest changes have been pushed to Github we can go ahead and deploy with Capistrano:

cap production deploy

Assuming that all went to plan, what we should now do is set our app’s Rails unique secret key which it needs to encrypt its sessions. SSH into the server and cd to where your app code is stored then run ‘bundle exec rake secret’:

ssh -i ~/.ssh/id_rsa ubuntu@52.16.139.61
cd /var/www/my-app/current
bundle exec rake secret

The result of this is your secret key and needs to be set as an environment variable. Copy it and then open your bash shell profile file:

sudo nano ~/.profile

Add this to the end of the file and save:

export SECRET_KEY_BASE="the key you just copied goes here" 

Lastly, check that you can access your server:

curl 52.16.139.61 #replace with your ip or domain

And that should be it, my verbose yet concise tutorial (at least i think so) is complete. Hopefully it worked for you and you can give yourself a pat on the back!

Good luck.