There was a time when Python's various virtualization tools were enough to fit my needs. However, as I continued to build increasingly complex applications which relied on a variety of other system services, I started to notice moving from project to project was difficult. Each project required unique service configurations, and moving between them was a headache. What I needed was a way to virtualize the entire system, which is when I turned to Vagrant. After using Vagrant for the past few years, I've developed a simple template that helps me spin up new Django environments quickly.

Virtualization with RVM

Vagrant is written in Ruby, and I must admit, I'm not a fan. Despite my disinterest in the language, I recognize virtualization is an important best practice, and my Vagrant projects are no exception. In order to manage the additional Ruby gems I use in my projects, I turn to RVM. Similar to Python's virtualenv, RVM allows you to create isolated environments where you can safely install gems. One additional benefit to RVM is it also allows you to install multiple versions of Ruby on your system. Stop by the RVM project's homepage and run their one liner from terminal to install.

After you have RVM installed, create a new directory to hold your project, and add .ruby-version and .ruby-gemset files inside of it with the contents shown below.

mkdir projectname
cd projectname
echo "ruby-1.9.2-p320" > .ruby-version
echo "projectname.com" > .ruby-gemset

Explaining RVM in detail is out of scope for this article, but the .ruby-version and .ruby-gemset files basically tell RVM the version of Ruby to use, and the name of the gemset respectively. RVM will automatically note these files whenever you cd into a directory where they exist. In this case it will tell RVM to use Ruby 1.9.2 (which I've already installed with RVM), and use the gemset called "projectname.com" when we install any gems.

I use two additional Ruby gems for every project—bundler_ and berkshelf. Bundler is more or less the equivalent to Python's pip, and Berkshelf is a cookbook manager for Vagrant. Bundler reads dependencies from a file called Gemfile just as pip typically loads dependencies from a requirements.txt file. Likewise, Berkshelf reads it's dependencies from a Berksfile.

First, run gem install bundler from within the project directory in terminal. Next, create a file named Gemfile with the following contents:

source 'https://rubygems.org'

gem 'berkshelf'

We can now tell bundler to install the dependencies by running bundle install. This will install Berkself, which we'll now create a Berksfile for with the following contents:

site :opscode

cookbook "apt"
cookbook "build-essential"
cookbook "openssl"
cookbook "postgresql", git: "https://github.com/hw-cookbooks/postgresql"
cookbook "vim"

Lastly, we can tell Berkshelf to download our cookbooks by running berks install. Your directory should now look like the following:

projectname /
    .ruby-gemset
    .ruby-version
    Berksfile
    Gemfile

Configuring Vagrant with Chef

Vagrant has several different types of provisioners available, but we'll primarily be using Chef. Chef is complicated, and takes a notable amount of time to master. If you're like me, you want to focus on what matters–writing code–not learning every detail of a mammoth provisioning system like Chef. Thankfully, we don't need but a high level understanding to accomplish our needs.

To begin we'll do something you should be very familiar with if you've made it this far–make a configuration file. Guess what this one's called? Vagrantfile! You can create a sample Vagrantfile by running the command vagrant init, or you can make your own and add the following:

# -*- mode: ruby -*-
# vi: set ft=ruby :

VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|

  config.vm.box = "precise64"
  config.vm.box_url = "http://files.vagrantup.com/precise64.box"
  config.vm.network :forwarded_port, guest:8000, host: 8080

  config.vm.provision :chef_solo do |chef|
    chef.add_recipe "apt"
    chef.add_recipe "build-essential"
    chef.add_recipe "openssl"
    chef.add_recipe "postgresql::server"

    chef.json = {
      :postgresql => {
        :version => "8.4",
        :password => {
          :postgres => "password"
        }
      },
      :build_essential => {
        :compiletime => true
      }
    }
  end

  config.vm.provision :shell do |s|
    $script = <<-eos
      sudo -u postgres createdb development
      sudo apt-get -q -y install git-core python-virtualenv python-imaging python-dev
    eos

    s.inline = $script
  end

end

There are a few things worth noting here. For starters, config.vm.box is set to precise64 which is Ubuntu 12. If you prefer a different VM, then look for a box. I'm forwarding port 8000 from the guest machine (the virtual machine) to port 8080 on the host machine (your computer). Django will be running from 8000 on the VM, so this way we can access it from the host. The cookbooks we're using are apt (for Debian systems, you can remove if you're using something else), build-essential, openssl, and postgres. The chef.json holds configuration for the various recipes. In particular we're assigning the user postgres a password of "password" which will go in your Django project's settings.py. Not terribly secure, but this is a development box.

Following the configuration for Chef, I'm also enabling shell provisioning. The $script variable contains a few lines that are executed from the shell which will create a database named "development" as well as install a few essential packages including git, python-imaging, and python-virtualenv.

Finally, we can launch our VM. Since we're using Berkshelf, we'll also install it's plugin for Vagrant.

vagrant plugin install vagrant-berkshelf
vagrant up

If all goes well, after several minutes (especially if you haven't already downloaded the VM box) your VM will be launched, provisioned, and you can connect to begin Django project setup by running vagrant ssh. View this project template on github.

comments powered by Disqus