Tools We Use: Vagrant

May 15, 2014

I have a theory that the size and age of a development team is roughly proportional to the amount of time it takes to get a new hire bootstrapped. Sure, there are some docs on a wiki somewhere, but a wiki is so far removed from the code, it takes little effort to have them drift.

Brian Wilson's aging face

The dream is this: When a developer sits down in front a computer, they make sure they have their SSH key (or their SSH Agent running), check out the code and get to work. As the canonical development environment changes, their workspace should stay up to date. As Brian Wilson's aging face often asks: Wouldn't it be nice?

Well, good news, Brian Wilson's aging face. We can do it, and it's really not that hard. We're going to use Chef and Vagrant to string together a development environment inside a VM. We're going to treat it just like how we treat our code, and it's going to be good. Real good. Brian Wilson's early 20s face good.

Brian Wilson's early 20s face

Vagrant is a great tool for getting environments bootstrapped, but keep in mind that we're not building a one-time deal. The configuration is code which will be checked in, branched, updated, reviewed, and checked back in. The project should move in lock-step with the development team and should at any point describe the canonical development environment for whatever's being shipped.

Non-intro to Chef

If you're interested in learning Chef, awesome. I commend you. There's lots of resources that are nary a Google away. We're not going to dive in to it here. What is important to know is that it's a general purpose system for configuring servers and clusters. It'll work just as well in the context of a single VM as it will a fleet of machines. We're keeping that in mind.

We'll be using Chef to tell Vagrant how we want our VM set up. Each iota of effort we put in to this saves us many times over down the road. The goal is to have an engineer spent three times as long bootstrapping once to have it be next to free in the future.

Chef can be a bit confusing at first. If for nothing else, there's a grab bag of cute little cooking terms you need to map to actual ideas before you can get anything done. However, this sort of setup is a great way to get yourself and your team familiarized with it.

Project Layout

We'll keep a mindful eye towards the future and lay out our project so that we could vagrant up other sorts of environments that aren't strictly "development." For example, we could string together a configuration that would boot each component of a staging cluster. Here's something close to what we'll end up with:

 -- our-cookbooks
    |-- cookbooks
    |   |-- accounts
    |   `-- development
    |-- environments
    |   `-- development
    |       `-- Vagrantfile
    |-- roles
    |   `-- development.rb
    `-- third-party
        |-- apt
        |-- git
        `-- java

Hey, where's my private SSH key?

Not my problem. We're treating in this project like any other piece of code. Everyone will have access to it. Checking in your private key would be a terrible, horrible idea.

Instead what we're going to do is copy down an ~/.ssh/config that makes sure ssh forwarding is on. This will let you SSH other other boxes from your development box, while keeping your private key out of the hands of your co-workers. Not that they're terrible people. Just sayin'.

Making an Accounts cookbook

Well, let's dive right in. The first thing any development box is going to need to do is let developers log in. Genius, right?

This cookbook is going to get set up in our-cookbooks/cookbooks/accounts. We're not going to worry about using customization for the moment, and just make sure folks can log in.

We'll first set up the attributes file, which describes the default bits of data used to set up a cookbook. We're going to put each developer's account name, password, and location of their SSH public key in here. The paths referenced for the public keys are going to point to our-cookbooks/cookbooks/accounts/files/default/, for example.

# file: our-cookbooks/cookbooks/accounts/attributes/default.rb
default[:accounts][:users] = {
  'frank' => {
    :public_key     => 'ssh/',
    :password_hash  => '$1$meO.HbBi$IDdn73F3UIHQG96qK3ShB7'
  'jimmy' => {
    :public_key     => 'ssh/',
    :password_hash  => '$1$cNePua/r$n57SdQF.97I5KGw2cLXV.0'

Next, we need to create a recipe that will slurp in these attributes and set up the system correctly. We're going to toss everyone in the admin group. Why? So they get unfettered sudo. And because I said so.

# file: our-cookbooks/cookbooks/accounts/recipes/default.rb
gem_package "ruby-shadow" do
  action  :install

# We're iterating over the attributes we've set up in file above. Check out how
# the shell parameter may be overriden to get a sense of how to add your own
# goodies.
node[:accounts][:users].each_pair do |username, params|
  group username

  user username do
    home      "/home/#{username}"
    shell     params[:shell] || "/bin/bash"
    password  params[:password_hash]
    supports  :manage_home => true

  directory "/home/#{username}/.ssh" do
    owner   username
    group   username
    mode    0700
    action  :create

  cookbook_file "/home/#{username}/.ssh/config" do
    source  "ssh/config"
    owner   username
    group   username
    mode    0600

  cookbook_file "/home/#{username}/.ssh/authorized_keys2" do
    source  params[:public_key]
    owner   username
    group   username
    mode    0600

group 'admin' do
  members   node[:accounts][:users].keys
  append    true

Setting up the role

To better the odds that we'll ever be able to re-use any of the work, we're going to lump together all the recipes for our development environment together in a single role.

# file: our-cookbooks/roles/development.rb
name        "development"
description "Development Environment"


Other Super-useful Vagrant commands you should know

vagrant provision Re-runs Chef on your running instance

vagrant destroy Kills the box and nukes its hard drive

vagrant halt Shuts the box down keeping its state around.

Penning the Vagrantfile

There's plenty of documentation on Vagrant's site on all the knobs you can tweak for a Vagrantfile, but we'll go with a pretty simple one below. It'll pull down a Ubuntu Precise 64 bit "box", give it 2G of RAM, and set it up with the development role that we hand-crafted with love, care, and Brian Wilson's aging face.

A little trick below is that if I'm logged in as mchadwick, it'll look for an identically named recipe and add that to the run list. If I want to carry around my vim config, this is a dandy spot for it. Added bonus: Add your personal cookbook to all sorts of other roles. Need your zsh set up just the way you like it on the staging stack? Why not? You're a developer. Do what you need to do.

# file: our-cookbooks/environments/development/Vagrantfile do |config|             = "precise64"
  config.vm.box_url         = ""
  config.vm.host_name       = "development"
  config.ssh.forward_agent  = true

  config.vm.forward_port "ui", 8080, 8080

  config.vm.customize do |vm|
    vm.memory_size = 2048 = "staging"

  config.vm.provision :chef_solo do |chef|
    chef.cookbooks_path = ["../cookbooks", "../third-party"]
    chef.roles_path     = "../roles"

    chef.json = {
      :user => ENV['USER']

    chef.add_role "development"

    chef.add_recipe ENV['USER'] if"cookbooks/#{ENV['USER']}")

With this in place, now all we should have to do is:

$ vagrant up

The occasional vagrant destroy and vagrant up is a great way to test your configuration by fire. There will be worse times to do it.

And we should be able to log in to our box. I'll leave implementing the actual recipes as an exercise to the reader. Just remember, as the code changes, vagrant provision early and vagrant provision often.


There's not enough information in this article to get you started with Vagrant. The bigger idea to present is going to great lengths to minimize friction in the development process. The goal is to give smart people a framework for spending as much of their time getting work done as possible.

If you're a fan of this sort of thing, take a gander at our open positions.