Setup Development Environment with Vagrant & Chef

I use Chef to manage and provision new staging and production servers in our company. It takes a lot of the headache out of managing multiple servers and allows me to fire up new web & data servers for our projects with ease. I have several cookbooks that I use to configure servers and to setup/configure websites. In a nutshell, it's rad, and website deployments have never been easier.

For my local development environment I currently run Ubuntu, with Apache, Nginx, PHP 5.3, Ruby 1.9.3, Ruby 2.0, MySQL 5.5, etc. Some of our projects use Node, Redis, MongoDB. Ideally I would offload all these different servers into virtual machines suited and designed for the task, identical in configuration ot the staging and production servers.

Enter Vagrant. Vagrant is a tool to configure development environments.

How I expect this to work:

  • I want to use my native development tools (NetBeans, Sublime Text, Git, etc) on my workstation.
  • I want to use the VM to serve the project files.
  • I do not want to have to deploy my local code to the VM for testing and review.
  • I will mount my project directory as a shared path in the VM.
  • I will build the VM using my Chef cookbooks.

Ok, not so bad. Vagrant makes this really easy.

Install Vagrant

wget https://dl.bintray.com/mitchellh/vagrant/vagrant_1.6.3_x86_64.deb 
sudo dpkg -i vagrant_1.6.3_x86_64.deb

Once installed you want to add a box to build your VM from. There are many to choose from. I prefer CentOS for my servers, and will add one from http://www.vagrantbox.es/. All of our production machines use CentOS or RHEL, so my development VM should use CentOS.

vagrant box add https://github.com/2creatives/vagrant-centos/releases/download/v6.4.2/centos64-x86_64-20140116.box --name centos-6.4

Now you need to create your Vagrant project. I have considered creating the Vagrant config file in my project and putting it under version control. Currently I just have a directory for Vagrant projects. Either way.

cd ~/projects/mywebproject.com
vagrant init centos-6.4

This will create a Vagrant config file. The Vagrant file describes how to build the VM. It defines the network settings, shared directories, and how to provision the machine using Chef. When creating the vagrant file you can pass the name of the box to use. I used the box we added earlier, "centos-6-4". If you leave this parameter off you can always edit the Vagrant file to change it.

Configuration is rather minimal. Not a whole lot we need to do to get something running. Open Vagrantfile in your text editor.

I want my VM to use the local network. You could opt to use the private network, which I believe is the default. You can also setup port forwarding here. For example, if you want to forward requests to http://localhost:8080 to port 80 on the VM. I just setup a public network for my VMs because often times I have people in the office review work on my server from their

config.vm.network :public_network

Let's set up the shared folders. The first path is the local directory you want to share, and is relative from the Vagrantfile. The second is the mount point in the VM. Since the Vagrant file is the root of my project, I will set the share directory to the current directory. Set the mount point to someplace you will want to serve your project from. I tend to do things the Enterprise Linux way, and will put my web projects under /var/

config.vm.synced_folder "./", "/var/www/mywebproject.com"

Now, to tell Vagrant how to provision the VM using

config.omnibus.chef_version = :latest
	config.vm.provision :chef_solo do |chef|
		chef.cookbooks_path = "/home/user/Development/my-chef/cookbooks"
		chef.roles_path = "/home/user/Development/my-chef/roles"
		chef.data_bags_path = "/home/user/Development/my-chef/data_bags"
		chef.add_recipe "my-cookbook::role-apache-server"
		chef.add_recipe "my-cookbook::role-mysql-server"
		chef.add_recipe "my-cookbook::role-php-server"
		chef.add_recipe "my-cookbook::site-my-kickass-site.com"

		chef.json = {
		  "mysql" => {
		    "server_root_password" => "MyMysqlRootPassword",
		  }
		}

	end

This is pretty straight forward. We tell Vagrant where our chef data is stored, which recipes to run, and pass along any attributes we want Chef to use. I would like to explain how I organize my Chef cookbooks very briefly. I use a fat-recipe/sinny-role approach. I have a cloud server cookbook to manage AWS and Rackspace instances. I have role recipes, and site recipes. A role recipe defines how the node should act: is it an Nginx server? Or a MySQL server? Will it run PHP-FPM? And so on. Then I have a site recipe which is defines how the website will be configured. It creates an apache vhost file, sets up a PHP-FPM pool, creates an Nginx proxy to a NodeJS app. I have data bags that correspond to different environments to configure the site as well, so production uses a different hostname than staging, has a different MySQL configuration, and so on. Now when Chef runs, it detects the environment, loads the corresponding data bag, and configures the site and node.

There is one more step before we can start up our VM. We need to install the omnibus vagrant plugin.

vagrant plugin install vagrant-omnibus

The Omnibus Vagrant plugin automatically hooks into the Vagrant provisioning middleware. It will bootstrap the VM for us. It is required if you are going to provision the VM with chef.

Ok, when that is installed you can fire up vagrate:

vagrant up

And there we go. You can continue working on your project locally, but serve it using a VM configured identically to your production servers. Have fun with your kick ass new dev environment!

The resulting Vagrant file should look something like:

    VAGRANTFILE_API_VERSION = "2"

    Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|

        config.vm.box "centos-6.4"

        config.vm.network :public_network

        config.vm.synced_folder "./", "/var/www/mywebproject.com"
             
        config.omnibus.chef_version = :latest
        config.vm.provision :chef_solo do |chef|
            chef.cookbooks_path = "/home/user/Development/my-chef/cookbooks"
            chef.roles_path = "/home/user/Development/my-chef/roles"
            chef.data_bags_path = "/home/user/Development/my-chef/data_bags"
            chef.add_recipe "my-cookbook::role-apache-server"
            chef.add_recipe "my-cookbook::role-mysql-server"
            chef.add_recipe "my-cookbook::role-php-server"
            chef.add_recipe "my-cookbook::site-my-kickass-site.com"

            chef.json = {
              "mysql" => {
                "server_root_password" => "MyMysqlRootPassword",
              }
            }

        end
    end

Did you like this post? Let me know by sending me a message. Is there a topic you would like me to cover? Let me know about that too. I look forward to hearing from you!

Let's Connect!