A Brand New Day, by Thomas Hawk.

In the DevOps post, I said that Vagrant is a helpful tool that eases the creation and configuration of virtualized environments and make the “it works on my machine” an excuse from the past. Surely Vagrant is a tool that helps DevOps teams to achieve environment consistency. But you don’t need to practice DevOps to enjoy the benefits.

You know, as a developer, that is hard to maintain your development environment up to date when switching between different projects. Even tools like rbenv and phpenv (that helps supporting the coexistance of different versions of the same language in a machine), we still have problems when we need to use different versions of databases, application servers and other softwares that compound the dependency stack of the different projects.

Then you start compiling different versions of databases, creating scripts to start/stop services according to a project but with the passing of time you (can) ends with a machine so messy that the maintenance of it starts to become a nightmare.

Other problem is that you starts to feel your user space becoming less responsible in the course of time. The startup of your machine becomes slower, specially when you have a lot of software installed as service. If using an notebook with the power cord unplugged, your battery can be sucked faster.

Then you develop using Mac or Windows. That impressive new open source project? Sometimes it does not have (a good) support for it yet. When it have (using tools like MacPorts, Homebrew or Cygwin) you sometimes get stuck with an older version of a package.

I myself had problems like this. I have a Mac. In the last years I worked with a lot of different PHP-based projects and had a lot of problems to compile PHP with extensions that need libraries like PDFLib, ICU and libxml. Then a year ago I decided to help developing the Symfony2 framework, specifically the Locale component.

The Symfony2’s Locale component needs ICU since it uses the PHP’s ext/intl classes. But Fabien Potencier and the Symfony core developers had decided that the framework would not depends on non-core PHP features to run. So we needed to develop a way to emulate the used behaviors of the ext/intl classes in user land code. Then Igor Wiedler and me decided at the time to create the tests asserting against our implementation and the ext/intl implementation.

Then I needed to install some different versions of PHP and ICU to find which minimum version we would code for (we coded using ICU 4.2). I don’t wanted to compile all those softwares at mine machine (a Macbook with the ol’ but good MacOS X Leopard). I was thinking in switching back for some Debian-based distro as desktop.

Nothing beats a Linux distribution (and its various packages managers) when it comes for using open source projects for software development. But I still prefered Mac for the desktop environment. At the time I was already using VirtualBox for development and then I discovered Vagrant that made my life a lot easier. Since then I create a virtualized environment for every project. This post shows the way I personally use Vagrant.

Talk is cheap. Let’s go to the fun part!

Installation and the basic commands

Since Vagrant builds VirtualBox virtual machines, you’ll obviously need it installed (version 4.0.x or 4.1.x). Download a recent version of Vagrant, it have installers for Windows and MacOS X and packages for Debian, RedHat, and Arch Linux. Or you can install it using Rubygems:

$ sudo gem install vagrant

With Vagrant installed, you’ll have to add a box. A box is a tar package that contains a base virtual machine image. Start adding one of the official available base boxes of Vagrant:

$ vagrant box add lucid32 http://files.vagrantup.com/lucid32.box
$ vagrant box add lucid64 http://files.vagrantup.com/lucid64.box

When executing one of the commands above, the chosen base box will be downloaded from the Vagrant site. The first argument of the vagrant box add command is the name of the box in your Vagrant environment. You’ll use it to specify which box you want to use when creating a virtualized environment. You can list the available boxes at your Vagrant environment with the vagrant list command:

$ vagrant box list
lucid32
lucid64

After finishing the download, create your first virtualized environment using Vagrant:

$ vagrant init lucid32
A `Vagrantfile` has been placed in this directory. You are now
ready to `vagrant up` your first virtual environment! Please read
the comments in the Vagrantfile as well as documentation on
`vagrantup.com` for more information on using Vagrant.

We will look at the Vagrantfile in the next topic. As the message above state, just run the vagrant up command:

$ vagrant up

Then log in to the VM:

$ vagrant ssh
vagrant@lucid32:~$

Vagrant automatically set up a VirtualBox shared folder in the path /vagrant to the directory of the Vagrantfile:

vagrant@lucid32:~$ ls /vagrant/
Vagrantfile

After stopping using the virtualized enviroment, you can suspend (pause) or halt (shut down) it. First, do the log off:

vagrant@lucid32:~$ exit
$ vagrant status
Current VM states:

default                  running

The VM is running. To stop this VM, you can run `vagrant halt` to
shut it down forcefully, or you can run `vagrant suspend` to simply
suspend the virtual machine. In either case, to restart it again,
simply run `vagrant up`.

Then run the vagrant suspend or the vagrant halt command:

$ vagrant suspend
[default] Saving VM state and suspending execution...
$ vagrant status
Current VM states:

default                  saved

To resume this VM, simply run `vagrant up`.
$ vagrant halt
[default] Discarding saved state of VM...
$ vagrant status
Current VM states:

default                  poweroff

The VM is powered off. To restart the VM, simply run `vagrant up`

The Vagrantfile

The Vagrantfile is where all the definitions of a VM are. You can set up any definition of the VirtualBox VM. To set up your VM to use 512 MB of RAM, set the following:

config.vm.customize [
  "modifyvm", :id,
  "--memory", "512",
  "--name",   "My First Vagrant box"
]

If you want to use another base box, change the config.vm.box value to the name of the box:

config.vm.box = "lucid64"

Now imagine you’re developing a web application and that you want to let your folks at the same network to access it. You can set a static IP for this:

config.vm.network :hostonly, "192.168.33.11"

As we saw before, Vagrant configures the VM to use a VirtualBox shared folder automatically at the path /vagrant. While VirtualBox shared folders can be useful, its performance degrades quickly while the number of files in the shared folder increases. I do recommend using NFS and skipping the shared folders altogether (you need set the VM to use a static IP, like the previous example shows):

# Relative path
config.vm.share_folder("v-root", "/vagrant", ".", :nfs => true)

# Absolute path
config.vm.share_folder("v-root", "/vagrant", "/Users/eriksencosta/Dev/my-project", :nfs => true)

After changing your Vagrantfile, you’ll need to restart the virtual machine for the changes to take effect:

$ vagrant halt
$ vagrant up

If using the NFS shared folder, you’ll be prompted for administrator rights, as Vagrant will modifies the /etc/exports file to rightly configure the NFS server on the host machine.

It is the provisioners support that makes Vagrant a really useful tool. At this point, you saw how to launch a new VM and some basic setup but something was missing. For developing an application, we commonly need other softwares installed like programming languages, databases and the like. But the installation process must be repeatable and more importantly, automated.

Vagrant supports provisioning using Chef (Solo and Server), Puppet (Standalone and Server) and Shell. Its Chef and Puppet support is a valuable feature for learning and testing Chef’s Cookbooks and Puppet’s Modules. For simplicity, let’s provision a LAMP environment using the Shell provisioner.

Creating a LAMP environment using the Shell provisioner

Let’s create a LAMP environment. We’ll install Apache, MySQL and PHP packages available at Ubuntu’s repositories, some general development tools (Git, Subversion), PHP tools (Phing, Composer), PHP QA tools (PHPUnit, PDepend) and a Drupal specific tool (Drush). Download or clone the vagrant-shell-scripts Git repository. Then add the following snippet to your Vagrantfile:

Vagrant::Config.run do |config|
  config.vm.provision :shell, :path => "/path/to/vagrant-shell-scripts/db-mysql.sh"
  config.vm.provision :shell, :path => "/path/to/vagrant-shell-scripts/dev-tools.sh"
  config.vm.provision :shell, :path => "/path/to/vagrant-shell-scripts/php5.sh"
  config.vm.provision :shell, :path => "/path/to/vagrant-shell-scripts/php5-qa.sh"
  config.vm.provision :shell, :path => "/path/to/vagrant-shell-scripts/php5-tools.sh"
  config.vm.provision :shell, :path => "/path/to/vagrant-shell-scripts/drush.sh"
end

After saving the file, run the vagrant up command:

$ vagrant up

Vagrant will show you the progress of the provisioning. After finishing, you’ll have a fully configured environment. Log in and try to run the phpunit command!

Note: after halting a machine that have provision definitions, running vagrant up will execute the provisioning again. If you just want to run all the provisioning again, use the option --no-provision:

$ vagrant up --no-provision

The Vagrantfile is what makes the process of creating VM environments repeatable. It’s the infrastructure as code part of Vagrant. Add it to your project’s repository to share it with your team, then every developer will only need to run vagrant up to use the same working enviroment.

And don’t forget to read the excelent documentation available at the Vagrant’s site!