Ruby on Rails, Nginx, Unicorn, Postgresql, Capistrano

03.21.2015

This is a step-by-step guide for anyone who is trying to learn the basics of setting up Ruby on Rails, Nginx, Unicorn, and Postgresql to run on a VPS and use Capistrano to automate deployment. For this tutorial, we will use Digital Ocean as the VPS provider because it is cheap, fast, and simple to use.

Software versions used in this tutorial:

Ruby 2.1.3
Rails 4.2.0
Postgresql 9.3.6
Ubuntu 14.04 x64
Nginx 1.4.6
Unicorn 4.8.3
Capistrano 3.4.0

For reference, here is the git repo of this deploydemo app:

https://github.com/travisluong/deploydemo

Step 1: Preparation.

Spin up a Digital Ocean droplet with Ubuntu 14.04 x64.

Create a new repository on GitHub or Bitbucket. You’ll need this for Capistrano to pull from during deploys.

Step 2: Create a simple Rails app.

Create a new rails app. We will set the database to postgresql.

user@local $ rails new deploydemo -d postgresql

Commit and push app to git origin.

user@local $ cd deploydemo
user@local $ git init
user@local $ git add .
user@local $ git commit -m "initial commit"
user@local $ git remote add origin git@github.com:travisluong/deploydemo.git
user@local $ git push origin master

Run bundle to install dependencies.

user@local $ bundle

Create postgresql database for development.

user@local $ createdb deploydemo_development

Create a rails scaffold

user@local $ bin/rails g scaffold post title content:text
user@local $ bin/rake db:migrate

Set the root to the scaffold index in config/routes.rb.

root 'posts#index'

commit the changes.

user@local $ git add .
user@local $ git commit -m “scaffold"

Step 3: Install and configure Capistrano and Unicorn.

Add these gems to Gemfile.

gem 'unicorn'
group :development do
  gem 'capistrano-rails'
  gem 'capistrano-rvm'
  gem 'capistrano3-unicorn'
end

Run bundle.

user@local $ bundle

Install Capistrano. This will create some files.

user@local $ cap install

In config/deploy.rb, set the application, repo_url, deploy_to with your settings. You can uncomment linked_files, and linked_dirs. You might also want to set format to pretty and log level to info to get rid of some unimportant (failed) messages from Capistrano.

set :application, 'deploydemo'
set :repo_url, 'git@github.com:travisluong/deploydemo.git'
set :deploy_to, '/var/www/deploydemo'
set :linked_files, fetch(:linked_files, []).push('config/database.yml', 'config/secrets.yml')
set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', 'public/system')
set :format, :pretty
set :log_level, :info

Add require statements in Capfile. This will make it work with RVM and add extra rake tasks to Capistrano.

require 'capistrano/rails'
require 'capistrano/rvm'
require 'capistrano3/unicorn'

Add server to config/deploy/production.rb. You can find the server IP from your Digital Ocean dashboard. You’ll also want to set the unicorn_rack_env to production, since the gem uses “deployment” environment by default for some reason.

server '162.xxx.xxx.xx', user: 'deploy', roles: %w{app db web}
set :unicorn_rack_env, -> { "production" }

Commit changes.

user@local $ git add .
user@local $ git commit -m "capistrano"

Create a the unicorn configuration file in config/unicorn/production.rb.

working_directory '/var/www/deploydemo/current'
pid '/var/www/deploydemo/current/tmp/pids/unicorn.pid'
stderr_path '/var/www/deploydemo/log/unicorn.log'
stdout_path '/var/www/deploydemo/log/unicorn.log'
listen '/tmp/unicorn.deploydemo.sock'
worker_processes 2
timeout 30

before_fork do |server, worker|
  old_pid = "/var/www/microsweepstakes/current/tmp/pids/unicorn.pid.oldbin"
  if old_pid != server.pid
    begin
    sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
    Process.kill(sig, File.read(old_pid).to_i)
    rescue Errno::ENOENT, Errno::ESRCH
    end
  end
end

Here is what this file is doing:

  1. Set the working directory of the app.
  2. Set the unicorn pid file location. That contains the id of the unicorn process.
  3. The unicorn log files.
  4. The socket file that connects to Nginx.
  5. The number of workers each master process will spawn.
  6. The amount of time a request is given before Unicorn kills the process.
  7. A before fork that kills the old process when a new process is started, so we can achieve zero downtime deploys.

Commit changes and push to origin.

user@local $ git add .
user@local $ git commit -m "unicorn"
user@local $ git push origin master

Step 4: Create a deploy user on your VPS.

ssh in to remote server.

user@local $ ssh root@162.xxx.xxx.xx

Create a deploy user and give sudo privileges. You can leave all the fields blank, except for password.

root@remote $ adduser deploy
root@remote $ adduser deploy sudo

Switch to deploy user.

root@remote $ sudo su deploy

Step 5: Set up all of the SSH keys.

CD into home and make a .ssh directory.

deploy@remote $ cd
deploy@remote $ mkdir .ssh

Copy your ssh key from local over to remote for password-less login.

user@local $ cat ~/.ssh/id_rsa.pub | ssh -p 22 deploy@162.xxx.xxx.xx 'cat >> ~/.ssh/authorized_keys'

Follow the instructions in the link below to add an ssh key to GitHub for your remote VPS. You need to do this so that Capistrano can pull the application from GitHub on deploys. The commands in this guide should be run on the VPS as your deploy user.

https://help.github.com/articles/generating-ssh-keys/

Step 6: Install packages and dependencies.

Run update.

deploy@remote $ sudo apt-get update

Install packages.

deploy@remote $ sudo apt-get install -y curl git-core build-essential zlib1g-dev libssl-dev libreadline-dev libyaml-dev libsqlite3-dev sqlite3 libcurl4-openssl-dev libxml2-dev libxslt1-dev python-software-properties

Install node.js for JavaScript runtime.

deploy@remote $ sudo apt-get install -y nodejs

Install Postgresql. libpq-dev is needed for building the pg gem later.

deploy@remote $ sudo apt-get install -y postgresql postgresql-contrib libpq-dev

Install nginx. If you navigate to your IP in your browser, you should see an nginx welcome page.

deploy@remote $ sudo apt-get install -y nginx

Step 7: Set up the postgres user and create production database.

Create the production database.

deploy@remote $ sudo -u postgres createdb deploydemo_production

Set up password for “postgres” user.

deploy@remote $ sudo -u postgres psql
postgres=# \password postgres
postgres=# \q

Step 8: Install RVM, ruby, and bundler.

Add a line to your .gemrc to turn off document generation. Document generation takes way too long.

deploy@remote $ echo "gem: --no-document" >> ~/.gemrc

Install rvm and ruby.

deploy@remote $ gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
deploy@remote $ \curl -sSL https://get.rvm.io | bash -s stable
deploy@remote $ source /home/deploy/.rvm/scripts/rvm
deploy@remote $ rvm install 2.1.3

Install bundler.

deploy@remote $ gem install bundler

Step 9: Create the directories and shared files needed for Capistrano deployment.

Make /var/www directory in remote.

deploy@remote $ sudo mkdir /var/www

Change the owner of /var/www to deploy.

deploy@remote $ sudo chown deploy /var/www

Make the shared config directory.

deploy@remote $ mkdir -p /var/www/deploydemo/shared/config

Make log directory for unicorn log.

deploy@remote $ mkdir -p /var/www/deploydemo/log

Create the shared database.yml file that will be shared between releases.

deploy@remote $ sudo vim /var/www/deploydemo/shared/config/database.yml
production:
  adapter: postgresql
  encoding: unicode
  pool: 5
  timeout: 5000
  database: deploydemo_production
  username: postgres
  password: password
  host: localhost

Run rake secret to generate a secret key. You will put that in the shared secrets.yml file.

user@local $ bin/rake secret

Create the shared secrets.yml file and put in the secret key you generated in the last step.

deploy@remote $ sudo vim /var/www/deploydemo/shared/config/secrets.yml
production:
  secret_key_base: 94d04182d80fe4ea1ec41b6839b019a02e8a3f8cfa0696ee3b5281d5512473c8483334b23f31bd7fcdf3914263d0719c819494613e3d6ffb1792a45b6277da66

Add the RAILS_ENV variable to .bashrc so Unicorn can load the right environment.

deploy@remote $ echo 'export RAILS_ENV=production' >> ~/.bashrc
deploy@remote $ source ~/.bashrc

Run cap production deploy:check to make sure all your files and directories are in place. Everything should be successful.

user@local $ cap production deploy:check

Step 10: Configure and restart Nginx.

Back up the default file. I put it in home directory for now.

deploy@remote $ sudo mv /etc/nginx/sites-available/default ~

Create a new nginx default file with these settings. Note that the socket is the same one specified in the Unicorn configuration.

deploy@remote $ sudo vim /etc/nginx/sites-available/default
upstream app {
  server unix:/tmp/unicorn.deploydemo.sock fail_timeout=0;
}
server {
  listen 80;
  server_name localhost;
  root /var/www/deploydemo/current/public;
  try_files $uri/index.html $uri @app;
  location @app {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_pass http://app;
  }
  error_page 500 502 503 504 /500.html;
  client_max_body_size 4G;
  keepalive_timeout 10;
}

Restart nginx.

deploy@remote $ sudo service nginx restart

Step 11: Deploy the app.

Run cap production deploy. First time will take a while since it has to run bundle and install all dependencies.

user@local $ cap production deploy

Start the unicorn workers.

user@local $ cap production unicorn:start

You should see your simple CRUD app when you go to your IP in the browser.

Conclusion

I hope you have found this guide helpful. If you see any improvements that can be made to this guide or have any questions, feel free to contact me.

Notes

If you’re having issues with symlinks, deleting the “current” symlink and running deploy again might help.

If there’s an error, look at the Capistrano output. There’s usually some information that can help you debug the problem.

Use the cap -T command to see all Capistrano commands.

Use ps aux | grep unicorn to check unicorn processes. Capistrano has some commands to stop and restart unicorn workers, but use kill [pid] to kill the process manually if needed.

Sources

http://blog.mccartie.com/2014/08/28/digital-ocean.html

http://www.gotealeaf.com/blog/deploy-rails-apps-with-capistrano/

http://matteodepalo.github.io/blog/2013/03/07/how-i-migrated-from-heroku-to-digital-ocean-with-chef-and-capistrano/

https://www.digitalocean.com/community/tutorials/how-to-use-roles-and-manage-grant-permissions-in-postgresql-on-a-vps–2

http://voiceofchunk.com/2014/06/09/deploying-rails-apps-using-passenger-rbenv-postgresql-and-mina/

http://stackoverflow.com/questions/6282307/execjs-and-could-not-find-a-javascript-runtime

http://stackoverflow.com/questions/9987171/rails-3-2-fatal-peer-authentication-failed-for-user-pgerror

https://www.digitalocean.com/community/tutorials/how-to-use-mina-to-deploy-a-ruby-on-rails-application

https://www.infinum.co/the-capsized-eight/articles/faster-web-application-deployments-using-mina-instead-of-capistrano

https://coderwall.com/p/yz8cha/deploying-rails-app-using-nginx-unicorn-postgres-and-capistrano-to-digital-ocean

http://sirupsen.com/setting-up-unicorn-with-nginx/

http://theflyingdeveloper.com/server-setup-ubuntu-nginx-unicorn-capistrano-postgres/

http://www.cubicleapps.com/articles/ubuntu-rails-ready-with-nginx-unicorn

http://benjaminknofe.com/blog/2014/03/08/zero-downtime-deployment-with-unicorn-and-capistrano/

http://stackoverflow.com/questions/19716131/usr-bin-env-ruby-no-such-file-or-directory-using-capistrano-3-capistrano-rben

http://stackoverflow.com/questions/21692601/capistrano-3-process-failing

http://railscasts.com/episodes/335-deploying-to-a-vps