#335 Deploying to a VPS pro
Deploying a Rails application can be overwhelming because there are so many different options. Here I present a pattern for deploying a Rails app to a VPS using nginx, Unicorn, PostgreSQL, rbenv and more.
- Download:
- source codeProject Files in Zip (98 KB)
- mp4Full Size H.264 Video (48.2 MB)
- m4vSmaller H.264 Video (22.3 MB)
- webmFull Size VP8 Video (21.7 MB)
- ogvFull Size Theora Video (51.3 MB)
Resources
The Stack
- Ubuntu (10.04 LTS)
- nginx (1.0.14)
- Unicorn (4.2.0)
- PostgreSQL (9.1.3)
- Postfix
- rbenv
- rbenv-installer
Note: You may find it necessary to manually fix the Nginx init script as shown here.
Server Setup
terminal
apt-get -y update apt-get -y install curl git-core python-software-properties # nginx add-apt-repository ppa:nginx/stable apt-get -y update apt-get -y install nginx service nginx start # PostgreSQL add-apt-repository ppa:pitti/postgresql apt-get -y update apt-get -y install postgresql libpq-dev sudo -u postgres psql # \password # create user blog with password 'secret'; # create database blog_production owner blog; # \q # Postfix apt-get -y install telnet postfix # Node.js add-apt-repository ppa:chris-lea/node.js apt-get -y update apt-get -y install nodejs # Add deployer user adduser deployer --ingroup admin su deployer cd # rbenv curl -L https://raw.github.com/fesplugas/rbenv-installer/master/bin/rbenv-installer | bash vim ~/.bashrc # add rbenv to the top . ~/.bashrc rbenv bootstrap-ubuntu-10-04 rbenv install 1.9.3-p125 rbenv global 1.9.3-p125 gem install bundler --no-ri --no-rdoc rbenv rehash # get to know github.com ssh git@github.com # after deploy:cold sudo rm /etc/nginx/sites-enabled/default sudo service nginx restart sudo update-rc.d -f unicorn_blog defaults
Local Setup
terminal
rails new blog -d postgresql rails g scaffold article name content:text # setup Git mate .gitignore cp config/database.yml config/database.example.yml git init git add . git commit -m "initial commit" git remote add origin git@github.com:ryanb/blog.git git push # add Capistrano, Unicorn, and nginx config bundle capify . chmod +x config/unicorn_init.sh git add . git commit -m "deployment configs" # ssh setup cat ~/.ssh/id_rsa.pub | ssh deployer@72.14.183.209 'cat >> ~/.ssh/authorized_keys' ssh-add # -K on Mac OS X # deployment cap deploy:setup # edit /home/deployer/apps/blog/shared/config/database.yml on server cap deploy:cold
Gemfile
gem 'unicorn' gem 'capistrano'
Capfile
load 'deploy/assets'
config/deploy.rb
require "bundler/capistrano" server "72.14.183.209", :web, :app, :db, primary: true set :application, "blog" set :user, "deployer" set :deploy_to, "/home/#{user}/apps/#{application}" set :deploy_via, :remote_cache set :use_sudo, false set :scm, "git" set :repository, "git@github.com:ryanb/#{application}.git" set :branch, "master" default_run_options[:pty] = true ssh_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}" 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_init.sh /etc/init.d/unicorn_#{application}" run "mkdir -p #{shared_path}/config" put File.read("config/database.example.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" end
Other Files
Alternative Server Setup
terminal
# Apache (instead of nginx) apt-get -y install apache2 a2enmod rewrite # after deploy: sudo a2dissite default sudo a2ensite blog sudo /etc/init.d/apache2 reload # MySQL (instead of PostgreSQL) apt-get -y install mysql-server mysql-client libmysqlclient-dev mysql -u root -p # create database blog_production; # grant all on blog_production.* to blog@localhost identified by 'secret'; # exit # Compile Ruby (instead of rbenv) sudo apt-get -y install build-essential zlib1g-dev libssl-dev libreadline5-dev libyaml-dev wget ftp://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.3-p125.tar.gz tar -xvzf ruby-1.9.3-p125.tar.gz cd ruby-1.9.3-p125/ ./configure --prefix=/usr/local make sudo make install sudo gem install bundler --no-ri --no-rdoc # Phusion Passenger (instead of Unicorn) sudo apt-get -y install libcurl4-openssl-dev apache2-prefork-dev libapr1-dev libaprutil1-dev sudo gem install passenger --no-ri --no-rdoc sudo passenger-install-apache2-module sudo vim /etc/apache2/apache2.conf # modify as instructed by installer
Alternative Local Setup
config/deploy.rb
# ... namespace :deploy do task :start do; end task :stop do; end task :restart, roles: :app, except: {no_release: true} do run "touch #{deploy_to}/current/tmp/restart.txt" end task :setup_config, roles: :app do sudo "ln -nfs #{current_path}/config/apache.conf /etc/apache2/sites-available/#{application}" run "mkdir -p #{shared_path}/config" put File.read("config/database.example.yml"), "#{shared_path}/config/database.yml" puts "Now edit the config files in #{shared_path}." end after "deploy:setup", "deploy:setup_config" # ... end
config/apache.conf
<VirtualHost *:80> # ServerName example.com # ServerAlias *.example.com DocumentRoot /home/deployer/apps/blog/current/public <Directory "/home/deployer/apps/blog/current/public"> Options FollowSymLinks AllowOverride None Order allow,deny Allow from all </Directory> </VirtualHost>