Deploying Your Rails App to the Cloud with Unicorn, Nginx, and Capistrano
We all know how easy it is to create a Rails app, but what about when your app is ready for production?
The first thing you need to do is to set up your server and install the proper libraries, so fire up a terminal window and SSH into your server, after you have done this you will need to execute a few commands.
Configuring the server
Once you’re done configuring your server you need to install Ruby, for this you can either use RVM or rbenv, but I’m going to leave this choice to you.
Uploading to github
Next we’re going to upload our app to github. Head on over to the github and create a new repository. Once you’ve done this, fire up a terminal on your local machine and “cd” into the root of your rails app. Now, I’m going to assume you already have some experience with git commands. If not, never fear, we’ve got this covered. Once you’re in the root of your rails app run the following commands:
git initgit add .git commit -m "<message>"git remote add origin git@github.com:<username>/<git repo>.gitgit push origin masterWhat we’ve done here is initialize an empty git repository inside the root of your rails app and add all your files to the git repo (If you need to ignore files you can edit the .gitignore file.) Next, we provided a message to the commit and added the remote origin, which will track our local development. Finally, we pushed the changes in our master branch to github.
Installing Capistrano
We’re almost there! It”s time to setup Capistrano for deployment, and the first step is too add it to the Gemfile:
gem 'capistrano'
Save the Gemfile and run the
bundle
command to install the gem. Next we’re going to capify our project, on a terminal window run:
capify .
This will create 2 files: one is the Cap file which will be placed in your root folder. By the way, if you’re using the rails asset pipeline, you will need to uncomment the line -load ‘deploy/assets’-. The second file is the deploy.rb file which will be placed inside the config folder of your rails app, In deploy.rb you will configure all the commands capistrano will be running on our remote server. You can copy and paste the example provided here:
require "bundler/capistrano"
# Define your server hereserver "<server>", :web, :app, :db, primary: true
# Set application settingsset :application, "<app_name>"set :user, "<deployment_user>" # As defined on your serverset :deploy_to, "/home/#{user}/apps/#{application}" # Directory in which the deployment will take placeset :deploy_via, :remote_cacheset :use_sudo, false
set :scm, "git"set :repository, "git@github.com:<git_user>/#{application}.git"set :branch, "master"
default_run_options[:pty] = truessh_options[:forward_agent] = true
after "deploy", "deploy:cleanup" # keep only the last 5 releases
namespace :deploy do %w[start stop restart].each do |command| desc "#{command} unicorn server" task command, roles: :app, except: {no_release: true} do run "/etc/init.d/unicorn_#{application} #{command}" # Using unicorn as the app server end end
task :setup_config, roles: :app do sudo "ln -nfs #{current_path}/config/nginx.conf /etc/nginx/sites-enabled/#{application}" sudo "ln -nfs #{current_path}/config/unicorn_ini.sh /etc/init.d/unicorn_#{application}" run "mkdir -p #{shared_path}/config" put File.read("config/database.yml"), "#{shared_path}/config/database.yml" puts "Now edit the config files in #{shared_path}." end after "deploy:setup", "deploy:setup_config"
task :symlink_config, roles: :app do run "ln -nfs #{shared_path}/config/database.yml #{release_path}/config/database.yml" end after "deploy:finalize_update", "deploy:symlink_config"
desc "Make sure local git is in sync with remote." task :check_revision, roles: :web do unless `git rev-parse HEAD` == `git rev-parse origin/master` puts "WARNING: HEAD is not the same as origin/master" puts "Run `git push` to sync changes." exit end end before "deploy", "deploy:check_revision"endAs you can see, this deploy file uses unicorn as the server (which I recommend because of its stability, but you can use the server of your choice.) After you are finished setting up your deploy file with your custom configuration, create two files under your config folder: one named nginx.conf, and another one named unicorn.rb. Feel free to use the examples provided here:
Nginx.conf
upstream unicorn { server unix:/tmp/unicorn.<app_name>.sock fail_timeout=0;}
server { listen 80 default deferred; server_name <your_servername>; if ($host = '<your_servername>' ) { rewrite ^/(.*)$ http://<your_servername>/$1 permanent; } root /home/deployer/apps/<app_name>/current/public;
location ^~ /assets/ { gzip_static on; expires max; add_header Cache-Control public; }
try_files $uri/index.html $uri @unicorn; location @unicorn { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_redirect off; proxy_pass http://unicorn; }
error_page 500 502 503 504 /500.html; client_max_body_size 4G; keepalive_timeout 10;}Unicorn.rb
# Define your root directoryroot = "/home/deployer/apps/gifroll/current"
# Define worker directory for Unicornworking_directory root
# Location of PID filepid "#{root}/tmp/pids/unicorn.pid"
# Define Log pathsstderr_path "#{root}/log/unicorn.log"stdout_path "#{root}/log/unicorn.log"
# Listen on a UNIX data socketlisten "/tmp/unicorn.gifroll.sock"
# 16 worker processes for production environmentworker_processes 16
# Load rails before forking workers for better worker spawn timepreload_app true
# Restart workes hangin' out for more than 240 secstimeout 240Final Steps
Create one final file under your config folder called unicorn_ini.sh edit the file and copy and paste the following code:
#!/bin/sh### BEGIN INIT INFO# Provides: unicorn# Required-Start: $remote_fs $syslog# Required-Stop: $remote_fs $syslog# Default-Start: 2 3 4 5# Default-Stop: 0 1 6# Short-Description: Manage unicorn server# Description: Start, stop, restart unicorn server for a specific application.### END INIT INFOset -e
# Feel free to change any of the following variables for your app:TIMEOUT=${TIMEOUT-60}APP_ROOT=/home/deployer/apps/<app_name>/currentPID=$APP_ROOT/tmp/pids/unicorn.pidCMD="cd $APP_ROOT; bundle exec unicorn -D -c $APP_ROOT/config/unicorn.rb -E production"AS_USER=<user>set -u
OLD_PIN="$PID.oldbin"
sig () { test -s "$PID" && kill -$1 `cat $PID`}
oldsig () { test -s $OLD_PIN && kill -$1 `cat $OLD_PIN`}
run () { if [ "$(id -un)" = "$AS_USER" ]; then eval $1 else su -c "$1" - $AS_USER fi}
case "$1" instart) sig 0 && echo >&2 "Already running" && exit 0 run "$CMD" ;;stop) sig QUIT && exit 0 echo >&2 "Not running" ;;force-stop) sig TERM && exit 0 echo >&2 "Not running" ;;restart|reload) sig HUP && echo reloaded OK && exit 0 echo >&2 "Couldn't reload, starting '$CMD' instead" run "$CMD" ;;upgrade) if sig USR2 && sleep 2 && sig 0 && oldsig QUIT then n=$TIMEOUT while test -s $OLD_PIN && test $n -ge 0 do printf '.' && sleep 1 && n=$(( $n - 1 )) done echo
if test $n -lt 0 && test -s $OLD_PIN then echo >&2 "$OLD_PIN still exists after $TIMEOUT seconds" exit 1 fi exit 0 fi echo >&2 "Couldn't upgrade, starting '$CMD' instead" run "$CMD" ;;reopen-logs) sig USR1 ;;*) echo >&2 "Usage: $0 <start|stop|restart|upgrade|force-stop|reopen-logs>" exit 1 ;;esacWe have all the files we need, so let’s setup the server to deploy our app. Push the latest changes to github and after that run the following command:
cap deploy:setup
This will create 2 folders under the /<user>/apps/<app_name> on your server. as well as some links for the unicorn and nginx configration files to place them in the proper location. After the setup is complete “cd” into the /<user>/apps/<app_name>/shared/config and edit the database.yml file to work with your database.
Once you’ve done this, give the server access to the git repository by running the following command on our development machine:
ssh-add
Now we’re all setup and ready to deploy. Let’s try this out by running:
cap deploy:cold
And watch it go. A “cold” deploy will run the migrations and do a server start and restart. In my experience, it will rarely work the first time you will usually get some errors here and there. The errors will usually point you in the right direction.
If everything went OK, congratulations! You just deployed your app using unicorn, nginx and capistrano. Hope you liked it!
Are you serious? And this methodology is considered viable for production environments? I'm a .Net developer who has dabbled and enjoyed learning Rails, and would love to use it for projects but it's this sort of thing that freaks enterprise clients into sticking with more familiar stacks, e.g., ASP.NET/IIS.
I know there is limitation for uploads in Nginx by default. But my files wont exceed 1m.
Any clue? Thanks.
Here is my nginx.conf