Building Your Own Baseline Vagrant Box
I’ve had a long-running open source crush on Vagrant - kind of like a fanboy from a distance. I’ve long fantasized about conjuring up a pristine development server on my local workstation, provisioning some software to put it into a production-like state, and having it start serving requests for my application. And even though I’ve worked on several projects where it would have been a great fit, I’ve never found the time to sit down and really dig into it until now. The work I’m planning for BetterFBO will probably require me to set up infrastructure components that are a bit outside my usual development stack, so it seems like now is the time to dive a little deeper into Vagrant and Chef in order to better manage the configuration and avoid installing these pieces directly on my workstation.
Initial provisioning of virtual machines can take some time and experimentation, so my initial intermediate goal was to package up a box that would serve as a starting point for all future VMs. Fortunately, Vagrant makes this pretty simple.
Step 1: Install and Set Up Vagrant
I’ve had Vagrant installed for a long time, but it was in need of an upgrade. It used to be that this was a matter of installing the gem, but over time Mitchell has tried to steer the product toward a more general audience rather than just Ruby developers, and he’s been releasing platform-specific installation packages. Grab the right one for your OS of choice and install it on your system. When it’s done, you should have a vagrant
executable installed somewhere on your $PATH
which you can test out by typing vagrant -h
on the command line. It should respond by printing out something like the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
Step 2: Install a Base Box
Next, you’ll need to install a Vagrant box to serve as the starting point for your own. These can be found at the old Vagrantbox.es, which is a public directory of privately hosted boxes, or the new VagrantCloud, which is currently in beta and looks like it will be a box hosting service using a free-paid hybrid business model similar to GitHub. I generally use Ubuntu Server for production deployments, so I’m starting with a the most recent long-term support (LTS) version of that which happens to be 12.04 Precise Pangolin. In order to save myself some time, I created a base box with a baseline set of software packages installed and configured to save myself time during provisioning and called it chriskottom-precise64.box
and added it to Vagrant with the command:
1
|
|
Step 3: Set Up Bundler
You’ll start by setting up your Gemfile with two gems. Chef is what you’ll use for provisioning software and managing the configuration on the virtual machines in your development environment, while Berkshelf provides a way of managing the cookbooks you’ll use for that purpose. In the end, your Gemfile should look like this:
1 2 3 4 |
|
Step 4: Write a Chef Cookbook to Install the Required Software
Chef makes it pretty easy to define instructions for provisioning hardware in the form of cookbooks which define the instructions, configuration parameters, and templates needed to put a machine into some desired state. The syntax and libraries it provides for defining the instructions, known in Chef lingo as recipes, offers a platform-independent way of performing common tasks like installing packages, creating scheduled tasks, controlling users and groups, and so on. Complete specs of the DSL syntax and the available resources can be found on the Chef documentation site, but for now, we’ll define a simple cookbook in just a few steps.
Chef is packaged with a command-line tool known as knife that provides a bunch of utility functions for working with the software. Here we’ll use it to create a new cookbook called chriskottom_precise64
in the directory cookbooks
with the following one-liner:
1
|
|
The resulting directory structure in the cookbook directory you specified is comprehensive:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
For the purposes of this exercise, we only retain three files - metadata.rb
, README.md
, and recipes/default.rb
- and the attributes/
directory, so you can delete everything else.
In attributes/
you’ll want to create a file called default.rb
and use it to define the array of package names you want to install. This is what my list looks like:
1 2 3 4 5 6 7 |
|
Then we only need to set up the default recipe to install all the packages on the list:
1 2 3 4 5 6 |
|
Step 5: Set Up Berkshelf
Berkshelf wasn’t a tool that I’d used before, but you can think of it as being like Bundler but for managing Chef cookbooks instead of Ruby gems with similar command line semantics and a manifest format similar to the one used for Gemfiles.
Set up your own Berksfile manifest by entering berks init
on the command line and declare the cookbooks you’ll need to provision your environment by editing the Berksfile. It should end up looking like this:
1 2 3 4 5 6 |
|
This is a simple example, but it showcases examples of Berkshelf’s ability to acquire cookbooks from a variety of resources: from the Opscode Community repository, from a GitHub repository, or from a local directory source.
Berkshelf implements a berks install
command that can be used to download and install all the required cookbooks and their dependencies, but because the two are used together often, they’ve developed a Vagrant plugin that provides direct integration and handles the downloading of required cookbooks whenever you provision a VM. Install the plugin by typing vagrant plugin install vagrant-berkshelf
.
It’s worth a brief comment here explaining why I’m installing the rbenv' and
ruby_build` cookbooks. The Ubuntu distribution I’m using as a baseline was originally released in 2012, and the new LTS won’t drop for a few weeks from this writing and won’t be installed on my production servers for a while after that. While these releases are still suitable and supported, they do contain some antiquated software including old versions of Ruby that won’t run some more recently developed Chef cookbooks. In order to get around this, I replace the bare-metal Rubies installed on most Vagrant base boxes with something more recent, and I use rbenv and ruby-build to manage the Ruby installations on my machines since it provides a little more ease of use in development and all other environments.
Step 6: Set Up Vagrant
Vagrant virtual machines are configured by way of a Vagrantfile in the project directory. You can create an initial template with the vagrant init
command and edit it like so:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
|
Let’s look at what I’ve done here:
1. I specified that I want to use the precise64
box as the basis for all created virtual machines.
2. I activate the Berkshelf plugin to manage Chef cookbook dependencies.
3. I indicate that I want to use the :chef-solo
provisioner when setting up the VM.
4. I specify a list of Chef recipes that should be executed, in order, on the newly created VM.
5. I provide a Hash of parameters that the recipes will need to do their work.
(Note: Yes, I’m aware that Ruby 2.1.1 has been released, but some combination of Ubuntu Precise, the libraries that are installed, and Ruby 2.1.1 seems to make Nokogiri compilation choke. Downgrading Ruby to 2.1.0 seemed to solve the problem.)
Step 6: Vagrant UP!
It’s time to light this thing up with vagrant up
.
Just to set expectations, the first run will take some time (dependent on your system specs and your connection throughput) due to the need to install loads of voluminous software, but when it finishes, you’ll have a running VM with all the required software.
Step 7: Package the Box
Vagrant lets you package up a running VM as a box from the command line with one line:
1
|
|
The finished product should be a portable tar file called chriskottom-precise64.box
containing the box configuration and disk image.
Step 8: Add the Box to Vagrant
Before the new box can be used as the basis for another project, you’ll need to import it into Vagrant’s library using the following command:
1
|
|
And that’s it. Future projects can now refer to the chriskottom-precise64
box and have a VM that is recently updated and running a current version of Ruby.
I put the code up in a GitHub repo for anyone who’s interested. Feel free to use it for your own explorations or as the basis for building something awesome.