Vagrant is a great tool for creating a portable and configurable virtual machines on top of Virtual Box, VMware, AWS, and even Docker.

Vagrant is mainly used by developers as it is very easy to install and use, and more suitable for development environments than production. Vagrant can be used with shell scripting or other configuration management tools like: Ansible, Chef, Puppet, etc. as provisioners to install and configure the machine.

In this post, i will introduce how to build a simple PHP development box using vagrant with Ansible and shell script as provisioners, i will not get into the details on how to use vagrant, you can find more information on this topic here.

Install Dependencies

In order for vagrant to work, it needs a virtualization layer to work on, vagrant supports VirtualBox out of the box. To install VirtualBox on Ubuntu:

$ sudo apt-get install virtualbox

To use Ansible as provision method, we need to install it on host machine:

$ sudo apt-get install software-properties-common
$ sudo apt-add-repository ppa:ansible/ansible
$ sudo apt-get update
$ sudo apt-get install ansible

The next step is to install Vagrant, vagrant is available for Linux, Windows, and Mac OS X, you can download it from here.

Getting Started

Let’s start by creating the directory structure for the development box, the Directory will contain the Vagrantfile which is a special file that contains the detailed configuration of the machine(s) that will be created.

The directory will contain also a directory for the provisioners (shell script and Ansible playbook).

# tree -L 2 phpdev/
.
|-- provisioners
|   |-- group_vars
|   |-- hosts
|   |-- playbook.yml
|   |-- provision.sh
|   `-- roles
`-- Vagrantfile

The Vagrant File

The Vagrantfile is a special Ruby file that contains several variable assignments which used by vagrant as a configuration for installation and provisioning of the VMs.

Vagrant.configure(2) do |config|

  config.vm.box = "ubuntu/trusty64"

  config.vm.network "forwarded_port", guest: 80, host: 8080
  config.vm.network "forwarded_port", guest: 3306, host: 33306
  config.vm.network "private_network", ip: "10.0.33.11"
  config.vm.synced_folder "application/", "/var/www/app", create: true

  config.vm.provider "virtualbox" do |vb|
    vb.gui = false
    vb.memory = "1024"
  end

  #config.vm.provision "shell", path: "provisioners/provision.sh"
  config.vm.provision "ansible" do |ansible|
    ansible.playbook = "provisioners/playbook.yml"
    ansible.inventory_path = "provisioners/hosts"
    ansible.limit = "all"
  end
end

The config.vm.box will define the base image for the box that will be used to provision the VM, the base box is the base image that will be cloned and reused for each project created.

Adding boxes to vagrant is simple as vagrant box add ubuntu/trusty64, this will download and install the Ubuntu 14.04 trusty image from the public Hashicorp catalog, note that the downloaded boxes will be added to the .vagrant.d directory inside the home directory.

The next 3 instructions config.vm.network, will modify the network configuration for the VM. There are 3 types of networking that works with vagrant:

  • forwarded_port: This option will forward a port on the host machine to another port on the guest VM, an example for that is to forward the port 8080 on the host to the port 80 on the guest machine.
  •  private_network: This option will implement what’s called Host-only network on the host machine, in our case i assigned a private ip (10.0.33.11) for the guest machine, the host machine will have the ip 10.0.33.1 so that it can access the guest VM on this local subnet,
  • public_network: This option will implement a bridged network on the VirtualBox, note that bridged network not exist on all providers, when using VirtualBox as  a provider will result in creating a bridged network interface for the guest VM which will be given (Using DHCP, or Static) ip in the local network like any machine on your local lan.

The next option config.vm.synced_folder is very important, it will result in syncing a directory on the host machine with the another directory on the guest VM, this will give the developer the ability to just pull his code using Git or FTP on the directory on host machine, and the ability to use your own IDE or text editor without interacting with the guest VM.

In our case we synced the application directory on the host machine to the /var/www/app on the guest VM, also adding create: true will create the directory on the host machine if it is not already exist.

The last instruction is config.vm.provision which will specify the provisioning method on the guest VM, the provisioning methods available are:

  • Shell Scripting
  • Ansible
  • Chef / Chef Solo
  • Puppet
  • Salt
  • Docker

Ansible As Provisioning Method

ansible-playbook command will be executed on the guest VM, the ansible playbook YAML file should be specified with ansible.playbook option inside the Vagrantfile.

This playbook consists of 4 roles:

  • common role: which will install the user key inside the machine, and disable root ssh login, and disable Password Authentication.
  • nginx role: which will install nginx and add the configuration files using the jinga2 template module.
  • phpfpm role: which will install php5-fpm and some php libraries that will be used with another components or can be used in future developments like php5-mysql.
  • mysql role: will install mysql server and client and and create an admin user.

I wont go on all the details about the playbook, but some notes worth to be mentioned, you can find the whole thing here on Github.

Ansible locate the machines using a special file called an inventory file, this file contains information about each machine, and user can assign variables to this machines and can be used in the roles later.

Vagrant can automatically create an inventory file to be used with ansible, or you can create your own inventory and specify its path using ansible.inventory_path inside the Vagrantfile.

The inventory file will look something like that:

[nginx]
10.0.33.11

[mysql]
10.0.33.11

[phpfpm]
10.0.33.11

I used group_vars directory to include all variables used inside the templates and inside the roles, the group_vars dir will contain file for each host in the inventory plus the all.yml file which contain the variables that can be used by all machines.

# tree -L 2 provisioners/
├── group_vars
│   ├── all.yml
│   ├── mysql.yml
│   ├── nginx.yml
│   └── phpfpm.yml
├── hosts
├── playbook.yml
├── provision.sh
└── roles
    ├── common
    ├── mysql
    ├── nginx
    └── phpfpm

Shell As Provisioning Method

To use shell script as a provisioner, comment the ansible section inside the vagrant file and un-comment this the shell scripting line. The shell script used to provision the machine:

#!/bin/bash
export DEBIAN_FRONTEND=noninteractive

sudo apt-get update -q

# Set Mysql Root Password
echo "Setting Mysql Root Password To ''"
sudo debconf-set-selections <<< 'mysql-server mysql-server/root_password password '
sudo debconf-set-selections <<< 'mysql-server mysql-server/root_password_again password '

# Install MySQL
sudo apt-get install -qqy -f mysql-server mysql-client

# Install Nginx And PHP-FPM And Git
sudo apt-get install nginx php5-fpm git-core -f -yqq

# Install PHP Libraries
sudo apt-get install php5-mysql php5-curl php5-gd php5-mcrypt php5-memcached -yqq

# Remove The Old Site Configuration
sudo rm /etc/nginx/sites-available/default

#Add Nginx Configuration File
sudo cat >> /etc/nginx/sites-available/default << EOF
server {
listen 80;

root /var/www/app;
index index.html index.htm index.php;

access_log /var/log/nginx/site.log;
error_log /var/log/nginx/site-error.log error;

server_name site.com;

charset utf-8;
location / {
try_files \$uri \$uri/ /index.php\$is_args$args;
}

location = /favicon.ico { log_not_found off; access_log off; }
location = /robots.txt { log_not_found off; access_log off; }

location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:///var/run/php5-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /var/www/app/\$fastcgi_script_name;
fastcgi_param PATH_INFO \$fastcgi_path_info;
fastcgi_param ENV development;
}
}
EOF
# Change The Document Root Owner
sudo chown www-data.www-data /var/www/app -R

# Restart Nginx
/etc/init.d/nginx restart

# Restart PHP-FPM
/etc/init.d/php5-fpm restart

exit 0

The script is very simple, it will install The whole development stack, and add the Nginx configuration file, you can adjust the script for your own needs like installing phpmyadmin or use postgres instead of MySQL.

Firing Up The Machine

Now its just a matter of vagrant up to create and provision the machine on VirtualBox, The Ansible will create a phpinfo script and place it inside the document root, the machine now can be accessed using the port 8080 or directly using the private ip address.

# vagrant up

phpinfo
Conclusion

Vagrant is a really simple yet powerful virtual machine provisioning tool, can be used to create and configure development environments. The machine can be provisioned using shell scripting or any configuration managment tool like Ansible or Chef.