Skip to content

Instantly share code, notes, and snippets.

@rchampourlier
Created October 12, 2011 15:23
Show Gist options
  • Save rchampourlier/1281506 to your computer and use it in GitHub Desktop.
Save rchampourlier/1281506 to your computer and use it in GitHub Desktop.
Capistrano recipe all-in-one: deploy rails app with rvm & bundler, run on unicorn, proxied by nginx
# Romain Champourlier © softr.li
# Inspired from many gist, recipes on github, tutorials... essentially:
# - https://gist.github.com/548927
# - http://techbot.me/2010/08/deployment-recipes-deploying-monitoring-and-securing-your-rails-application-to-a-clean-ubuntu-10-04-install-using-nginx-and-unicorn/
# - https://github.com/ricodigo/ricodigo-capistrano-recipes
#
# ONGOING WORK
# MIT License http://www.opensource.org/licenses/mit-license.php
#
# - Intended for Ubuntu 10.04.3
# - Deploy Rails app to be served by an unicorn instance, reverse-proxied by a nginx server
# - Automatically:
# - generates unicorn.rb conf file,
# - generates nginx host-file and symlinks it to sites-available and sites-enabled,
# - generates a startup script to run the unicorn instance when the server is booted,
# - adds the service to the expected runlevels.
# - adds a config file to monitor the unicorn instance in monit.
#
# TODO
# - use CLI for passwords
# - write a remove task
# Common
$:.unshift File.expand_path("..", __FILE__)
require 'capistrano'
require 'capistrano/cli'
# ------------------------------------------------------------------------------- #
# Project-specific
# ------------------------------------------------------------------------------- #
# Below are the parameters you must change to match your app.
server = "SERVER"
app_name = "APPNAME"
app_repository = "git@#{server}:#{app_name}.git"
app_repository_branch = "BRANCH"
app_deployment_root = "/PATH/TO/DEPLOYMENT/ROOT"
deployment_user = "USER"
deployment_user_password = "PASSWORD"
deployment_group = "GROUP"
rvm_ruby_string = "RVM-RUBY-STRING"
# You will be asked to enter domain names to deploy to. If you type nothing,
# this value is used.
default_nginx_domains = "DOMAIN_NAME"
# Below are some parameters you may want/need to change depending on your app
# requirements/server setup.
# Unicorn
# Number of workers (Rule of thumb is 1 per CPU)
# Just be aware that every worker needs to cache all classes and thus eat some
# of your RAM.
set :unicorn_workers, 2 # default: 4
#set :unicorn_workers_timeout, 30 # default: 30
# Nginx
#set :nginx_directory_path, "/etc/nginx" # default: /etc/nginx
#set :app_port, 80 # default: 80
#set :app_uses_ssl, false # default: false
#set :app_port_ssl, 443 # default: 443
# ------------------------------------------------------------------------------- #
$:.unshift(File.expand_path('./lib', ENV['rvm_path'])) # Add RVM's lib directory to the load path.
set :rvm_ruby_string, rvm_ruby_string # The RVM's env to run in.
require "rvm/capistrano" # Load RVM's capistrano plugin.
require 'bundler/capistrano'
set :use_sudo, false
set :git_shallow_clone, 1 # tell git to clone only the latest revision and not the whole repository
set :keep_releases, 5
set :application, app_name
set :repository, app_repository
set :branch, app_repository_branch
set :user, deployment_user
set :password, deployment_user_password
set :group, deployment_group
set :deploy_to, app_deployment_root
set :runner, deployment_user
set :scm, :git
set :rails_env, :production
role :app, server
role :web, server
role :db, server, :primary => true
# Options necessary to make Ubuntu’s SSH happy
ssh_options[:paranoid] = false
default_run_options[:pty] = true
# Shared paths
set :shared_path, "#{deploy_to}/shared"
set :configs_path, "#{shared_path}/configs"
set :pids_path, "#{shared_path}/pids"
set :sockets_path, "#{shared_path}/sockets"
set :logs_path, "#{shared_path}/log"
# Deploy hooked callbacks
after 'deploy:setup' do
sudo "mkdir -p #{configs_path}"
sudo "mkdir -p #{sockets_path}"
sudo "chown -R #{user}:#{group} #{configs_path} #{sockets_path}"
sudo "chmod -R g+w #{configs_path} #{sockets_path}"
unicorn.setup
nginx.setup
end
# Unicorn setup
# The wrapped bin to start unicorn. This is necessary because we're using rvm.
set :unicorn_binary, "unicorn"
set :unicorn_config, "#{configs_path}/unicorn.rb"
set :unicorn_pid, "#{pids_path}/unicorn.pid" # Defines where the unicorn pid will live.
set :unicorn_socket, "#{sockets_path}/unicorn.sock"
set :unicorn_workers, 4 unless exists?(:unicorn_workers)
# Workers timeout in the amount of seconds below, when the master kills it and
# forks another one.
set :unicorn_workers_timeout, 30 unless exists?(:unicorn_workers_timeout)
# Workers are started with this user/group
# By default we get the user/group set in capistrano.
set(:unicorn_user) { user } unless exists?(:unicorn_user)
set(:unicorn_group) { group } unless exists?(:unicorn_group)
# The unicorn template to be parsed by erb. You must copy this file to your app's vendor directory
# (vendor/unicorn_template.rb.erb). Capistrano will search it locally, so you don't need to track it
# in git. However, it may be helpful to have in there so anybody can use it to deploy.
set :unicorn_template, "vendor/unicorn_template.rb.erb"
set :unicorn_startup_script_template, "vendor/unicorn_startup_script_template.erb"
set :startup_script_prefix, "/etc/init.d"
set :startup_script_name, "unicorn_#{app_name}"
set :startup_script_path, "#{startup_script_prefix}/#{startup_script_name}"
set :unicorn_runlevels, "2 3 4 5"
set :unicorn_stoplevels, "0 1 6"
set :unicorn_startorder, "21"
set :unicorn_killorder, "19"
# The monit config template for the unicorn process. Expected in /vendor/monit_unicorn.conf.erb
set :monit_unicorn_template, "vendor/monit_unicorn.conf.erb"
set :monit_conf_prefix, "/etc/monit/conf.d"
set :monit_unicorn_conf_name, "unicorn_#{app_name}.conf"
set :monit_unicorn_conf, "#{monit_conf_prefix}/#{monit_unicorn_conf_name}"
# Unicorn deployment tasks
namespace :unicorn do
desc "Starts unicorn directly"
task :start, :roles => :app do
run "cd #{current_path} && bundle exec #{unicorn_binary} -c #{unicorn_config} -E #{rails_env} -D"
end
desc "Stops unicorn directly"
task :stop, :roles => :app do
run "#{try_sudo} kill `cat #{unicorn_pid}`"
end
desc "Restarts unicorn directly"
task :restart, :roles => :app do
run "#{try_sudo} kill -s USR2 `cat #{unicorn_pid}`"
end
desc "Gracefully stops unicorn directly"
task :graceful_stop, :roles => :app, :except => {:no_release => true} do
run "#{try_sudo} kill -s QUIT `cat #{unicorn_pid}`"
end
desc <<-EOF
Create the unicorn configuration file from the template and \
uploads the result to #{unicorn_config}, to be loaded by whoever is booting \
up the unicorn.
EOF
task :setup, :roles => :app , :except => { :no_release => true } do
generate_config(unicorn_template, unicorn_config)
# Generate the startup script and move it to the init.d dir (or any other directory specified
# by startup_script_prefix). Also set the correct rights.
generate_config(unicorn_startup_script_template, "#{shared_path}/#{startup_script_name}")
sudo "mv #{shared_path}/#{startup_script_name} #{startup_script_path}"
sudo "chown root:root #{startup_script_path}"
sudo "chmod 0755 #{startup_script_path}"
# Position the script for loading at server's boot.
sudo "update-rc.d #{startup_script_name} start #{unicorn_startorder} #{unicorn_runlevels} . stop #{unicorn_killorder} #{unicorn_stoplevels} ."
# Adds the monit config file for this process.
generate_config(monit_unicorn_template, "#{shared_path}/#{monit_unicorn_conf_name}")
sudo "mv #{shared_path}/#{monit_unicorn_conf_name} #{monit_unicorn_conf}"
sudo "chown root:root #{monit_unicorn_conf}"
sudo "chmod 0644 #{monit_unicorn_conf}"
end
end
# nginx setup
set :nginx_directory_path, "/etc/nginx" unless exists?(:nginx_directory_path)
set :app_port, 80 unless exists?(:app_port)
set :app_uses_ssl, false unless exists?(:app_uses_ssl)
set :app_port_ssl, 443 unless exists?(:app_port_ssl)
# The nginx template to be parsed by erb. You must copy this file to your app's vendor directory
# (vendor/nginx_template.rb.erb). Capistrano will search it locally, so you don't need to track it
# in git. However, it may be helpful to have in there so anybody can use it to deploy.
set :nginx_template, "vendor/nginx_template.rb.erb"
set :nginx_host_config, "#{configs_path}/#{app_name}.tld"
# Nginx tasks are not *nix agnostic, they assume you're using Debian/Ubuntu.
# Override them as needed.
namespace :nginx do
desc "Parses and uploads nginx configuration for this app"
task :setup, :roles => :app , :except => { :no_release => true } do
set :nginx_domains, Capistrano::CLI.ui.ask("Enter #{application} domain names:") { |q| q.default = default_nginx_domains}
generate_config(nginx_template, nginx_host_config)
sudo "ln -s #{nginx_host_config} #{nginx_directory_path}/sites-available/"
sudo "ln -s #{nginx_host_config} #{nginx_directory_path}/sites-enabled/"
end
desc "Parses config file and outputs it to STDOUT (internal task)"
task :parse, :roles => :app , :except => { :no_release => true } do
puts parse_config(nginx_template)
end
desc "Reload nginx. Send the HUP signal to have nginx reload its configuration"
task :reload, :roles => :app , :except => { :no_release => true } do
sudo "service nginx reload"
end
desc "Restart nginx"
task :restart, :roles => :app , :except => { :no_release => true } do
sudo "service nginx restart"
end
desc "Stop nginx"
task :stop, :roles => :app , :except => { :no_release => true } do
sudo "service nginx stop"
end
desc "Start nginx"
task :start, :roles => :app , :except => { :no_release => true } do
sudo "service nginx start"
end
desc "Show nginx status"
task :status, :roles => :app , :except => { :no_release => true } do
sudo "service nginx status"
end
end
namespace :deploy do
# Invoked during initial deployment
desc "start"
task :start, :roles => :app, :except => {:no_release => true} do
unicorn.start
nginx.restart # reload seems not to suffice
end
desc "stop"
task :stop, :roles => :app, :except => {:no_release => true} do
unicorn.stop
end
desc "reload"
task :reload, :roles => :app, :except => {:no_release => true} do
unicorn.reload
end
desc "graceful stop"
task :graceful_stop, :roles => :app, :except => {:no_release => true} do
unicorn.graceful_stop
end
# Invoked after each deployment afterwards
desc "restart"
task :restart do
stop
start
end
end
def parse_config(file)
puts File.expand_path(File.dirname(__FILE__))
require 'erb' # render not available in Capistrano 2
template = File.read(file) # read it
return ERB.new(template).result(binding) # parse it
end
# Generates a configuration file parsing through ERB
# Fetches local file and uploads it to remote_file
# Make sure your user has the right permissions.
def generate_config(local_file, remote_file)
temp_file = '/tmp/' + File.basename(local_file)
buffer = parse_config(local_file)
File.open(temp_file, 'w+') { |f| f << buffer }
upload temp_file, remote_file, :via => :scp
`rm #{temp_file}`
end
@rchampourlier
Copy link
Author

Update adds monit configuration file to monitor the unicorn process.
TODO: verify if monit does not need to be restarted to acknowledge the new process to monitor.

@papucho
Copy link

papucho commented Jun 12, 2013

This looks very nice. I'm going to test it.

@papucho
Copy link

papucho commented Jun 12, 2013

@rchampourlier Just noticed now that this is 2 years old.
I'm gona test this with ubuntu 12.04.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment