Ops

Think Globally, Stage Locally

Mike Perham ·

Or: how to create and deploy to a staging environment running locally!

Staging: an environment that duplicates production as closely as possible to find any lingering bugs before you update production. Most of the Rails community develops on OSX but deploys to Linux; this can be fragile since it is common to forget Linux-specific environment changes necessary for your app. At Carbon Five, most of our customers can afford to maintain a dedicated staging environment but for smaller projects, I wanted to have my own Linux staging environment without the cost of a real slice or EC2 instance.

In this post, I will show you how to create a Linux VM with Vagrant and use capistrano to deploy to your vagrant VM. My coworker Jared recently posted an nice intro to Vagrant, a great project by Mitchell Hashimoto to simplify and automate the use of virtual machines during development. You should read his post first as I’m not going to cover Chef, which I highly recommend for automating the provisioning of your VM. Using Chef means that your staging and production boxes can be virtually identical by using the exact same recipes to build them.

Let’s assume you have a Rails 3 app for which you want to create a staging VM. We’ll install Vagrant and configure it in the project like so:

  cd myapp
  # NOTE: Make sure you've installed VirtualBox first!
  gem install vagrant
  # Downloads a blank Ubuntu 11.04 64-bit image
  # Or find your own box on http://vagrantbox.es
  vagrant box add ubuntu-1104-server-amd64 http://dl.dropbox.com/u/7490647/talifun-ubuntu-11.04-server-amd64.box
  # Adds a Vagrantfile to your Rails app which talks to the new image
  vagrant init ubuntu-1104-server-amd64
  # Starts the new VM
  vagrant up
  # Adds SSH details to your SSH config so Capistrano can deploy directly to your VM
  vagrant ssh-config >> ~/.ssh/config
  # Logs into your new VM
  vagrant ssh
  # Perform a lot of Chef recipe work
  # ...Left as an exercise to the reader...

This whole process, minus the box download, should take a minute or two. Remember that you will need to do a bunch of Chef work to install the stack your application needs (e.g. Unicorn, JRuby, etc). Once that that is done, let’s work on the Capistrano configuration. In this case, I’m using the capistrano-ext gem to add multiple environment support so we can deploy to production or our new staging VM:

# config/deploy.rb
set :stages, %w(staging production)
set :default_stage, "staging"
require 'capistrano/ext/multistage'
require "bundler/capistrano"

set :user, 'vagrant'
set :application, "myapp"
set :deploy_to, "/home/#{user}/#{application}"
set :repository,  "git@github.com:acmeco/#{application}"

set :scm, :git
set :branch, "master"
set :deploy_via, :remote_cache
ssh_options[:forward_agent] = true

And in config/deploy/staging.rb:

# 'vagrant' = the hostname of the new VM
role :web, "vagrant"
role :app, "vagrant"
role :db,  "vagrant", :primary => true
set :rails_env, 'staging'

The secret sauce was in the vagrant ssh-config command, which configured ssh so it knows how to log into your new Vagrant VM. Now all we need to do is a simple cap staging deploy and Capistrano will use ssh to connect to the VM and have the VM pull your latest changes from your github repo.

Once deployed, you can tell Vagrant to forward your application’s port in the VM to a localhost port. In my case, I have Unicorn running on port 5000 in the VM, forwarded to port 8080 on localhost in OSX. With this configuration in my Vagrantfile, I can browse to http://localhost:8080 to hit my Rails app running in the VM.

  config.vm.forward_port "unicorn", 5000, 8080

Final note: if you have trouble contacting github, make sure you are running ssh-agent to handle key requests from the VM. This will allow the vagrant user in the VM to act as you when contacting github: run ssh-agent && ssh-add on your local machine (NOT in the VM).

Most of this post is typical Vagrant and capistrano configuration. With just a few simple tricks, we can tie the two together for great victory and hopefully more stability for your site. Good luck!