Creating Custom Drupal Development Machines with Vagrant and Ansible

When used together, two incredible tools—Ansible and Vagrant—can help you set up a custom local development environment for Drupal. I say "custom" because, frankly, there are several good existing configuration schemes out there. However, they don't have the flexibility needed to meet every use case and, luckily, it’s just the opposite with Ansible and Vagrant.

Ansible is a tool that enables you to script the setup of servers, including virtual machines. When used this way, it's similar to Puppet, Chef, Salt, and other provisioning tools. It's also an automation and orchestration tool, so you can replace ad-hoc scripts with Ansible playbooks and run them whenever, say, you need to do a rolling update of servers.

Vagrant is a wrapper around virtualization programs that makes them much easier to use. It has built-in support for a number of hypervisors (such as VirtualBox), and via plugins it can support even more. I use it most often with Parallels Desktop, provided by the vagrant-parallels community plugin.

Why custom?

We recently set up a developer VM (virtual machine) for a client and decided to go the custom route. Here were our primary reasons for going custom:

  • The client's servers ran Red Hat Enterprise Linux (RHEL) 6. The majority of existing configurations were designed for flavors of Ubuntu or Debian.
  • There would be a considerable learning curve to setting up some of the scripts that needed to be run on the VM, involving tools such as Capistrano and Seafile.
  • The client used a custom Seafile client program.
  • To access certain client servers, a specific SSH key needed to be placed on the machine.
  • The client's servers used a specific directory layout for shared files (they deployed Capistrano-style), and there were additional directories to be linked in for Drupal itself to work.

These requirements wouldn’t have been impossible to fulfill with a pre-built configuration, but considerable time would have been spent fighting how the configuration thought I should have been doing things. I’m going to walk you through how I brought in Ansible and Vagrant to solve this problem so that you can avoid this issue in the future.

For those who want to follow along

  1. Install Vagrant, install Ansible, and install VirtualBox on your machine. I won't explain every command I use in detail, but I recommend checking out the documentation (via installation links) if you get lost.
  2. FYI, if you're on Windows, you won't be able to install Ansible on your host machine. You might still find this article useful if you use Vagrant’s Shell provisioner to set up Ansible directly on the VM and run it like that, or you can use this information with a tool like Packer.
  3. You can see all the files I'll be talking about in this repository we've made available. Note that the configuration won't quite work as-is since I haven't included the file templates or the roles (although I have included an ansible.yml you could pass to ansible-galaxy install -r ansible.yml). Use it as a learning tool but not as your solution.

Getting started: Vagrant

Luckily, going into this, I already had the experience of manually configuring a virtual machine to use as my local development environment, so I already understood the desired result. Nonetheless, the first thing I did was create a TODO.md in the code repository which I would be running in the VM. With Vagrant, the most typical pattern is to place Vagrant and provisioning-related files directly in the same code repository that you want to run locally. Secondly, I listed out all the steps to take in order to set up the VM from scratch. This became the "spec."

The third step was to bootstrap the actual machine by running vagrant init. This created a Vagrantfile, and the default had code comments to help me customize it to my needs. Mine wound up like this: my Vagrantfile.

It's a little more advanced than the one vagrant init spits out, since my configuration supports 3 hypervisors and uses the vagrant-hostsmanager plugin to automatically set up /etc/hosts on the host and guest machines.

Getting started: Ansible

Near the end of the Vagrantfile I saw this section, aka the magic that tells Vagrant how to run Ansible:


 ###### ANSIBLE SETUP ########
 config.vm.provision "ansible" do |ansible|
   ansible.playbook = "lib/ansible/example-drupal.yml"
   ansible.extra_vars = {
       'vagrant_hostmanager_aliases' => (config.hostmanager.aliases.join ' '),
   }
   ansible.verbose = 'v'
 end

$script = <<SCRIPT
/bin/bash
echo 'CMSBox provisioning complete! Run `vagrant provision` to update your VM whenever CMSBox gets updated.'
echo 'Have you completed the final, manual setup steps already? If not, see the "Completing Setup" section in README.md.'
SCRIPT
 config.vm.provision "shell", inline: $script

The Vagrantfile also included a simple, inline shell script that prints out a success message if everything went alright with Ansible. If Ansible fails, it won't reach the shell script.

The Ansible Playbook

The Ansible playbook that actually gets run is https://bitbucket.org/popestepheng/vagrant-ansible-custom-drupal/src/a17.... It’s worth sharing that I found Ansible’s syntax fairly easy to read even before I knew how to write it myself. Every Ansible task can be given a name, which helps both developers and people running playbooks understand what's going on. It also automatically documents the steps to achieve a certain server configuration.

Here is a summary of what is happening in this file:

  • Additional CentOS software repositories are enabled. For me, this used its own play (a collection of Ansible steps to run on a specific set of, or all, servers) because I wanted to make sure all of this ran first, even before the pre_tasks: section in the second play.
    The software on the Vagrant base box is updated, SELinux is disabled (since this is a configuration for a development VM), various necessary software packages are installed, and standard system settings are configured.
  • A lot of software is installed through the use of Ansible roles; these are analogous to Drupal modules, and you can search for them at Ansible Galaxy (or, as I like to do, on Google and GitHub — Ansible Galaxy's user interface leaves something to be desired). Here, I'm basically installing all the stuff I need for Drupal and Capistrano.
  • Complete system-level setup: symlinks, workarounds, configuring system software, and so on.
  • User-mode setup is completed as the vagrant user, consisting of more configuration and symlinking, SSH stuff, RVM gems, and a Drupal cronjob.


Bringing it all together

So how do you actually run this? It’s simple: vagrant up.

This creates a VM, brings it up, and runs provisioning on it. If you need to SSH in, use vagrant ssh from the same directory as your Vagrantfile.

If something goes wrong with provisioning, run vagrant provision to try again. You can also start fresh by typing vagrant destroy and vagrant up again.

Wait, did you just tell me to destroy my local development environment?

Yes, I did! The beauty of Ansible is that you can rebuild it fairly quickly, so you don't have to worry as much about losing it or your time. You can backup your Drupal database, destroy/rebuild, and reload the database; you could even tell Ansible to pre-load the database from a particular file path and drop a backup there before rebuilding. If you mess up your VM somehow, you'll lose your database work in progress, but you won't have to spend hours getting going again. If you invest some time in a good configuration, you will be up again in minutes!

Ansible, FTW

Vagrant and Ansible let you create shareable, flexible, and automatically-documented local development configurations, which lends itself well to Drupal development. If something goes wrong, you can rebuild your development environment in minutes, not hours. You and your team will, as a result, focus more on development and less on being system administrators. Plus, once you go to setup your staging or production server, your Ansible skills will come in handy again; it will help your real servers match your virtual servers.

So if you’re not currently using something like Ansible (such as Chef, Puppet, Salt, Fabric, or another tool) yet, make sure you learn it as soon as you can. Trust me, you will wonder what you were doing with your life before it!