Derived from this link
The main difference is that this configuration supports rails 3.2.
To set up a server, do the following as root:
echo '--- Install system packages ---'
apt-get update
Let that finish. Then:
apt-get upgrade -y
Let that finish. Then:
apt-get install build-essential ruby-full libmagickcore-dev imagemagick libxml2-dev libxslt1-dev git-core postgresql postgresql-client postgresql-server-dev-8.4 nginx curl node
Let that finish. Then:
apt-get build-dep ruby1.9.1
Install node so you can do asset precompilation later:
sudo apt-get install python-software-properties && sudo add-apt-repository ppa:chris-lea/node.js && sudo apt-get update && sudo apt-get install nodejs nodejs-dev
Then make a deployer user:
useradd -m -g staff -s /bin/bash deployer
passwd deployer
Then fill in the deployer password.
Add this to /etc/sudoers:
%staff ALL=(ALL) ALL
Install system-wide rvm (as root)
bash -s stable < <(curl -s https://raw.github.com/wayneeseguin/rvm/master/binscripts/rvm-installer)
Install ruby
source /etc/profile.d/rvm.sh
rvm install 1.9.2
rvm use 1.9.2@mrarlo --create
gem install bundler
As your deployer user, add this to your ~/.rvmrc
rvm_trust_rvmrcs_flag=1
Add the following to /etc/environment to always run commands in production on this server.
RAILS_ENV=production
Setup postgres database:
sudo -u postgres createdb mrarlo_production
sudo -u postgres psql
Then execute the following SQL (use your own password):
CREATE USER mrarlo_production WITH PASSWORD 'isotope_bang';
GRANT ALL PRIVILEGES ON DATABASE mrarlo_production TO mrarlo_production;
Make sure this user can actually log in (by default, only local-system users can). Modify /etc/postgresql/9.1/main/pg_hba.conf
and change out the line requiring local users to use ident to md5.
local all all md5
Restart postgres
/etc/init.d/postgresql restart
Then set up nginx:
# /etc/nginx/sites-available/default
upstream mrarlo.com {
# fail_timeout=0 means we always retry an upstream even if it failed
# to return a good HTTP response (in case the Unicorn master nukes a
# single worker for timing out).
# for UNIX domain socket setups:
server unix:/tmp/mrarlo.com.socket fail_timeout=0;
}
server {
# if you're running multiple servers, instead of "default" you should
# put your main domain name here
listen 80 default;
# you could put a list of other domain names this application answers
server_name mrarlo.com;
root /home/deployer/apps/mrarlo.com/current/public;
access_log /var/log/nginx/mrarlo.com_access.log;
rewrite_log on;
location / {
#all requests are sent to the UNIX socket
proxy_pass http://mrarlo.com;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size 10m;
client_body_buffer_size 128k;
proxy_connect_timeout 90;
proxy_send_timeout 90;
proxy_read_timeout 90;
proxy_buffer_size 4k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k;
proxy_temp_file_write_size 64k;
}
# if the request is for a static resource, nginx should serve it directly
# and add a far future expires header to it, making the browser
# cache the resource and navigate faster over the website
# this probably needs some work with Rails 3.1's asset pipe_line
location ~ ^/(images|javascripts|stylesheets|system|assets)/ {
root /home/deployer/apps/mrarlo.com/current/public;
expires max;
break;
}
}
And then:
# /etc/nginx/nginx.conf
user deployer staff;
# Change this depending on your hardware
worker_processes 4;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
multi_accept on;
}
http {
types_hash_bucket_size 512;
types_hash_max_size 2048;
sendfile on;
tcp_nopush on;
tcp_nodelay off;
# server_tokens off;
# server_names_hash_bucket_size 64;
# server_name_in_redirect off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
gzip on;
gzip_disable "msie6";
# gzip_vary on;
gzip_proxied any;
gzip_min_length 500;
# gzip_comp_level 6;
# gzip_buffers 16 8k;
# gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
##
# Virtual Host Configs
##
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
Now start nginx (won't work till unicorn's up but go ahead)
/etc/init.d/nginx start
Add unicorn to your project's Gemfile:
# Gemfile
gem "unicorn"
group :development do
gem "capistrano"
end
Add unicorn config to your project:
# config/unicorn.rb
# Set environment to development unless something else is specified
env = ENV["RAILS_ENV"] || "development"
# See http://unicorn.bogomips.org/Unicorn/Configurator.html for complete
# documentation.
worker_processes 4
# listen on both a Unix domain socket and a TCP port,
# we use a shorter backlog for quicker failover when busy
listen "/tmp/my_site.socket", backlog: 64
# Preload our app for more speed
preload_app true
# nuke workers after 30 seconds instead of 60 seconds (the default)
timeout 30
pid "/tmp/unicorn.my_site.pid"
# Production specific settings
if env == "production"
# Help ensure your application will always spawn in the symlinked
# "current" directory that Capistrano sets up.
working_directory "/home/deployer/apps/my_site/current"
# feel free to point this anywhere accessible on the filesystem
user 'deployer', 'staff'
shared_path = "/home/deployer/apps/my_site/shared"
stderr_path "#{shared_path}/log/unicorn.stderr.log"
stdout_path "#{shared_path}/log/unicorn.stdout.log"
end
before_fork do |server, worker|
# the following is highly recomended for Rails + "preload_app true"
# as there's no need for the master process to hold a connection
if defined?(ActiveRecord::Base)
ActiveRecord::Base.connection.disconnect!
end
# Before forking, kill the master process that belongs to the .oldbin PID.
# This enables 0 downtime deploys.
old_pid = "/tmp/unicorn.my_site.pid.oldbin"
if File.exists?(old_pid) && server.pid != old_pid
begin
Process.kill("QUIT", File.read(old_pid).to_i)
rescue Errno::ENOENT, Errno::ESRCH
# someone else did our job for us
end
end
end
after_fork do |server, worker|
# the following is *required* for Rails + "preload_app true",
if defined?(ActiveRecord::Base)
ActiveRecord::Base.establish_connection
end
# if preload_app is true, then you may also want to check and
# restart any other shared sockets/descriptors such as Memcached,
# and Redis. TokyoCabinet file handles are safe to reuse
# between any number of forked children (assuming your kernel
# correctly implements pread()/pwrite() system calls)
end
Capify the project:
capify .
Replace config/deploy.rb with the following:
# config/deploy.rb
require "bundler/capistrano"
set :application, "mrarlo.com"
set :scm, :git
set :repository, "[email protected]:isotope11/mrarlo.com.git"
set :branch, "origin/master"
set :migrate_target, :current
set :ssh_options, { forward_agent: true }
set :rails_env, "production"
set :deploy_to, "/home/deployer/apps/mrarlo.com"
set :normalize_asset_timestamps, false
set :user, "deployer"
set :group, "staff"
set :use_sudo, false
role :web, "192.168.1.154"
role :app, "192.168.1.154"
role :db, "192.168.1.154", primary: true
set(:latest_release) { fetch(:current_path) }
set(:release_path) { fetch(:current_path) }
set(:current_release) { fetch(:current_path) }
set(:current_revision) { capture("cd #{current_path}; git rev-parse --short HEAD").strip }
set(:latest_revision) { capture("cd #{current_path}; git rev-parse --short HEAD").strip }
set(:previous_revision) { capture("cd #{current_path}; git rev-parse --short HEAD@{1}").strip }
default_environment["RAILS_ENV"] = 'production'
# Use our ruby-1.9.2-p318@my_site gemset
default_environment["PATH"] = "--"
default_environment["GEM_HOME"] = "--"
default_environment["GEM_PATH"] = "--"
default_environment["RUBY_VERSION"] = "ruby-1.9.2-p318"
default_run_options[:shell] = 'bash'
namespace :deploy do
desc "Deploy your application"
task :default do
update
restart
end
desc "Setup your git-based deployment app"
task :setup, except: { no_release: true } do
dirs = [deploy_to, shared_path]
dirs += shared_children.map { |d| File.join(shared_path, d) }
run "#{try_sudo} mkdir -p #{dirs.join(' ')} && #{try_sudo} chmod g+w #{dirs.join(' ')}"
run "git clone #{repository} #{current_path}"
end
task :cold do
update
migrate
end
task :update do
transaction do
update_code
end
end
desc "Update the deployed code."
task :update_code, except: { no_release: true } do
run "cd #{current_path}; git fetch origin; git reset --hard #{branch}"
finalize_update
end
desc "Update the database (overwritten to avoid symlink)"
task :migrations do
transaction do
update_code
end
migrate
restart
end
task :finalize_update, except: { no_release: true } do
run "chmod -R g+w #{latest_release}" if fetch(:group_writable, true)
# mkdir -p is making sure that the directories are there for some SCM's that don't
# save empty folders
run <<-CMD
rm -rf #{latest_release}/log #{latest_release}/public/system #{latest_release}/tmp/pids &&
mkdir -p #{latest_release}/public &&
mkdir -p #{latest_release}/tmp &&
ln -s #{shared_path}/log #{latest_release}/log &&
ln -s #{shared_path}/system #{latest_release}/public/system &&
ln -s #{shared_path}/pids #{latest_release}/tmp/pids &&
ln -sf #{shared_path}/config/database.yml #{latest_release}/config/database.yml
CMD
#precompile the assets
run "cd #{latest_release}; bundle exec rake assets:precompile"
if fetch(:normalize_asset_timestamps, true)
stamp = Time.now.utc.strftime("%Y%m%d%H%M.%S")
asset_paths = fetch(:public_children, %w(images stylesheets javascripts)).map { |p| "#{latest_release}/public/#{p}" }.join(" ")
run "find #{asset_paths} -exec touch -t #{stamp} {} ';'; true", env: { "TZ" => "UTC" }
end
end
desc "Zero-downtime restart of Unicorn"
task :restart, except: { no_release: true } do
run "kill -s USR2 `cat /tmp/unicorn.my_site.pid`"
end
desc "Start unicorn"
task :start, except: { no_release: true } do
run "cd #{current_path} ; bundle exec unicorn_rails -c config/unicorn.rb -D"
end
desc "Stop unicorn"
task :stop, except: { no_release: true } do
run "kill -s QUIT `cat /tmp/unicorn.my_site.pid`"
end
namespace :rollback do
desc "Moves the repo back to the previous version of HEAD"
task :repo, except: { no_release: true } do
set :branch, "HEAD@{1}"
deploy.default
end
desc "Rewrite reflog so HEAD@{1} will continue to point to at the next previous release."
task :cleanup, except: { no_release: true } do
run "cd #{current_path}; git reflog delete --rewrite HEAD@{1}; git reflog delete --rewrite HEAD@{1}"
end
desc "Rolls back to the previously deployed version."
task :default do
rollback.repo
rollback.cleanup
end
end
end
def run_rake(cmd)
run "cd #{current_path}; #{rake} #{cmd}"
end
Now there is one little thing you'll need to do. I like to run my apps, even on
the server, to use their own gemset. This keeps everything clean and isolated.
Login to the deployer account and create your gemset. Next run rvm info and fill
the PATH
, GEM_HOME
and GEM_PATH
variables accordingly.
Place the database configuration on the server in the shared directory.
# /home/deployer/apps/mrarlo.com/shared/config/database.yml
production:
adapter: postgresql
encoding: unicode
database: mrarlo_production
pool: 5
username: mrarlo_production
password: password
Modify the server to support password-based SSH logins by editing
/etc/ssh/sshd_config
and setting:
PasswordAuthentication yes
Then restart ssh:
/etc/init.d/ssh restart
Set up a deploy key for the deployer user on your git repository. As deployer run the following:
ssh-keygen
Then:
cat ~/.ssh/id_rsa.pub
Push that as a deploy key to the project on github.
SSH into github as deployer from the server, to let this box validate that the host is valid to connect to:
ssh [email protected]
Then run a capistrano setup for the project:
cap deploy:setup
You also might have to go onto the server and, as deployer, run:
mkdir ~/apps/mrarlo.com/shared/pids
Whenever you have a new feature developed in a feature branch, this is the process of deploying it:
Merge feature_branch into master
Run your tests to make sure everything is dandy.
Push master
Run `cap deploy`