Howto: Provision virtual machine with Vagrant

Suppose you are working on a project that requires some very specific package versions. Suppose you already have a newer version of those packages installed for another project and these versions will collide. This is a problem that many of us are facing today at work, at school, etc. This is where Vagrant comes into the picture.

H3. What is vagrant?
Vagrant is a tool that automatically deploys a virtual machine and provisions it based on user requirements. In order to get started with Vagrant, 2 applications need to be installed on your machine: Vagrant itself and VirtualBox. The applications could be found here and here.
Once you have these applications installed, you can start using them to fit your business needs! In the following tutorial, I will demonstrate how to get setup for a Django project using Vagrant.

1) In order to get up and running, you first need to create a so called Vagrantfile. To create it, just run:

mkdir /path/to/your/project/folder
cd /path/to/your/project/folder
vagrant init

vagrant init is going to initialize your current folder for use with Vagrant and create a Vagrantfile that will be used later on to create and provision your virtual environment.

2) Determine which base OS you would like to use for your project. To see which boxes come preconfigured for use with Vagrant, run:

vagrant box list

The output will look something like this:

lucid32   (virtualbox)
precise32 (virtualbox)

where lucid32 is an Ubuntu 10.04 32 bit instance and precise32 is an Ubuntu 12.04 32 bit instance

3) Modify Vagrantfile to make it look like this:

# -*- mode: ruby -*-
# vi: set ft=ruby :
 
# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
VAGRANTFILE_API_VERSION = "2"
 
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  # Every Vagrant virtual environment requires a box to build off of.
  config.vm.box = "precise32"
 
  # The url from where the 'config.vm.box' box will be fetched if it
  # doesn't already exist on the user's system.
  config.vm.box_url = "<a href="http://files.vagrantup.com/precise32.box"
 
">http://files.vagrantup.com/precise32.box"
 
</a>  # Create a forwarded port mapping which allows access to a specific port
  # within the machine from a port on the host machine. In the example below,
  # accessing "localhost:8080" will access port 80 on the guest machine.
  config.vm.network :forwarded_port, guest: 8000, host: 8080
 
  # Create a private network, which allows host-only access to the machine
  # using a specific IP.
  config.vm.network :private_network, ip: "192.168.33.10"
 
  # Enable provisioning with Puppet stand alone.  Puppet manifests
  # are contained in a directory path relative to this Vagrantfile.
  # You will need to create the manifests directory and a manifest in
  # the file base.pp in the manifests_path directory.
  #
  config.vm.provision :puppet do |puppet|
    puppet.manifests_path = "manifests"
    puppet.manifest_file  = "base.pp"
  end
 
  config.vm.provision :puppet do |puppet|
    puppet.manifests_path = "manifests"
    puppet.manifest_file  = "project.pp"
  end
end

What this configuration does is the following:
- Uses an Ubuntu 12.04 virtual machine as a base OS
- Configures networking by setting VM's IP to 192.168.33.10
- Specifies locations of provisioning files.

NOTE: The reason why I have 2 separate provisioning files specified is to abstract out general VM configuration from specific project configuration.

4) After you've configured Vagrantfile to your liking, create a folder manifests with 2 files in it: base.pp and project.pp
Here is how I configured those files:

Exec {
    path => [
        '/usr/local/sbin'
        , '/usr/local/bin'
        , '/usr/sbin'
        , '/usr/bin'
        , '/sbin'
        , '/bin'
    ]
}
 
$MySQL_password = '<a href="mailto:root@vagrant">root@vagrant</a>'
 
class debconf {
    exec { 'MySQL_root_PW':
        command => "echo mysql-server-5.5 mysql-server/root_password select ${MySQL_password} | debconf-set-selections"
    }
 
    exec { 'MySQL_root_PW_confirm':
        command => "echo mysql-server-5.5 mysql-server/root_password_again select ${MySQL_password} | debconf-set-selections"
    }
}
 
class packages {
    exec { 'Update Repositories':
        command => 'apt-get update'
    }
 
    package { 'Install git':
        name => 'git-core',
        require => Exec['Update Repositories']
    }
 
    package { 'Install MySQL Server':
        name => 'mysql-server',
        require => Exec['Update Repositories']
    }
 
    package { 'Install MySQL Client':
        name => 'mysql-client',
        require => Exec['Update Repositories']
    }
 
    package { 'Install unzip': 
        name => 'unzip',
        require => Exec['Update Repositories']
    }
 
    package { 'Install curl':
        name => 'curl',
        require => Exec['Update Repositories']
    }
 
    package { 'Install PIP':
        name => 'python-pip',
        require => Exec['Update Repositories']
    }
 
    package { 'Install VIM':
        name => 'vim',
        require => Exec['Update Repositories']
    }
 
    package { 'Install screen':
        name => 'screen',
        require => Exec['Update Repositories']
    }
}
 
class django {
    exec { 'Install Django':
        command => 'pip install Django==1.5.1',
        require => Package['Install PIP']
    }
 
    package {'Install python-mysqldb':
        name => 'python-mysqldb'
    }
}
 
 
class {'debconf':}
 
class {'packages':
    require => Class['debconf']
}
 
class {'django':
    require => Class['packages']
}

and

Exec {
    path => [
        '/usr/local/sbin'
        , '/usr/local/bin'
        , '/usr/sbin'
        , '/usr/bin'
        , '/sbin'
        , '/bin'
    ]
}
$MySQL_password = '<a href="mailto:root@vagrant">root@vagrant</a>'
 
class project {
 
    exec { 'Add mysql database DDE':
        command => "mysql -u root -p${MySQL_password} -e \"CREATE DATABASE IF NOT EXISTS DDE\"",
    }
 
    exec { 'Grant mysql database DDE to user django@%':
        command => "mysql -u root -p${MySQL_password} -e \"GRANT ALL PRIVILEGES ON DDE.* TO 'django'@'%' IDENTIFIED BY 'django'\"",
        require => Exec['Add mysql database DDE']
    }
 
    exec { 'Grant mysql database DDE to user <a href="mailto:django@localhost">django@localhost</a>':
        command => "/usr/bin/mysql -u root -p${MySQL_password} -e \"GRANT ALL PRIVILEGES ON DDE.* TO 'django'@'localhost' IDENTIFIED BY 'django'\"",
        require => Exec['Add mysql database DDE']
    }
 
}
 
class {'project':}

The first file is going to perform the following actions:
- Update $PATH to specify locations of most commonly used binaries
- Update apt sources
- Install MySQL Server with default credentials of root/root@vagrant, MySQL Client, Git, Screen, Curl, Vim, Unzip, PIP, Django, and MySQL library for Python.

The second file is going to create a database for use with our Django project.

5) Once all your files have been configured to your liking, run

 vagrant up
 

and watch the magic happen! In a few minutes your virtual environment is going to up, all of the define packages are going to be installed and you can start coding.

It is worth mentioning that all of your project files will be located in /vagrant folder in the virtual machine.

Now, you might be wondering how to access your project's web site from your computer. The answer is simple: simply point your local browser to http://localhost:8080

For more information on Vagrant, I urge you to go through the documentation on their website

As always, questions and comments are welcomed!

Comments