Vagrant is an ingenious tool for standardizing virtual machine configuration. Inventor Mitchell Hashimoto introduces the tool succinctly in The Tao of Vagrant. Let’s explore the power of Vagrant from the point of view of a developer, including legacy project maintenance.
Using a virtual machine to emulate an old environment
Before discussing Vagrant, consider the utility of a basic virtual machine (VM) for dealing with legacy projects. Imagine, for example, trying to get a Drupal 6 site running on your newish laptop. This hypothetical site relies on PHP 5.2, whereas the current stable version of PHP is 5.6.x.
Attempting to coerce your laptop to act like it’s 2008 (when D6 was released) may involve bloating your computer with old software, likely resulting in a setup that is subtly and fatally different from the site’s actual live environment (e.g. Ubuntu 12.04 LTS).
Of course, this is where virtualization software like VirtualBox comes in handy. You can emulate Ubuntu 12.04 by fetching the appropriate ISO and setting up a VM using VirtualBox. Then it’s simply a matter of installing and configuring Apache, MySQL and PHP (i.e. a classic LAMP server).
This approach is satisfactory, but in practice requires enough effort that you won’t want to do it again soon. By contrast, Vagrant offers a reproducible, reliable and shareable methodology.
brew install Caskroom/cask/virtualbox brew install Caskroom/cask/vagrant
mkdir ubuntu cd ubuntu vagrant init ubuntu/precise64
You should see output like this:
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.
Excluding comments, the new
Vagrant.configure(2) do |config| config.vm.box = "ubuntu/precise64" end
ubuntu/precise64 is a reference to 64-bit Ubuntu 12.04 LTS (codenamed Precise Pangolin), one of the many available Vagrant boxes.
Next, let’s launch the VM.
On the first execution, vagrant up will download the “box” (i.e. disk image) and cache it for future reference in
~/.vagrant.d/boxes. The box size in this case is just 407M (a bargain compared to a 670M ISO image). Once the command completes, the VM is running! You can see this in two ways. First, run:
The output should contain:
Secondly, open VirtualBox and see the status “Running…” indicated under the “ubuntu_…” machine.
By the way, the ubuntu part of the machine name is derived from the directory name where the
Vagrantfile resides (which we created above).
Connecting to the VM
Now that we are in the VM, let’s observe a couple of interesting things. First of all, run:
You will notice that
Vagrantfile is listed! This is, in fact, the very
Vagrantfile you created earlier. This happens because
/vagrant is a synced folder that reflects the ubuntu directory in the host machine.
eth0 Link encap:Ethernet HWaddr 08:00:27:18:10:7f inet addr:10.0.2.15 Bcast:10.0.2.255 Mask:255.255.255.0 inet6 addr: fe80::a00:27ff:fe18:107f/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:469 errors:0 dropped:0 overruns:0 frame:0 TX packets:315 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:49992 (49.9 KB) TX bytes:41647 (41.6 KB)
Under eth0 (the ethernet device), find inet addr which indicates VM’s IP address (e.g
Now, exit the VM:
Back in the host machine, try reaching the VM via ping. This pings the VM exactly 4 times:
ping -c 4 10.0.2.15
PING 10.0.2.15 (10.0.2.15): 56 data bytes Request timeout for icmp_seq 0 Request timeout for icmp_seq 1 Request timeout for icmp_seq 2 Request timeout for icmp_seq 3 --- 10.0.2.15 ping statistics --- 4 packets transmitted, 0 packets received, 100.0% packet loss
The VM is unreachable because, by default, networking is configured in Network Address Translation (NAT) mode. This creates a closed network between VirtualBox and the VM. Let’s change that, because it’ll be convenient to access the D6 site directly via a browser on the host machine.
Vagrant.configure(2) do |config| config.vm.box = "ubuntu/precise64" config.vm.network "private_network", type: "dhcp" end
To make these changes take effect, run:
This reboots the machine and re-interprets the
Vagrantfile. Next, log back in with SSH and re-run ifconfig:
vagrant ssh ifconfig
eth1 Link encap:Ethernet HWaddr 08:00:27:00:82:c4 inet addr:172.28.128.3 Bcast:172.28.128.255 Mask:255.255.255.0 inet6 addr: fe80::a00:27ff:fe00:82c4/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:4 errors:0 dropped:0 overruns:0 frame:0 TX packets:16 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:1830 (1.8 KB) TX bytes:2104 (2.1 KB)
Notice that there is a new virtual network card called eth1 with a new inet addr (e.g.
Now, exit the VM:
Try to ping the VM again:
ping -c 4 172.28.128.3
PING 172.28.128.3 (172.28.128.3): 56 data bytes 64 bytes from 172.28.128.3: icmp_seq=0 ttl=64 time=0.422 ms 64 bytes from 172.28.128.3: icmp_seq=1 ttl=64 time=0.339 ms 64 bytes from 172.28.128.3: icmp_seq=2 ttl=64 time=0.385 ms 64 bytes from 172.28.128.3: icmp_seq=3 ttl=64 time=0.363 ms --- 172.28.128.3 ping statistics --- 4 packets transmitted, 4 packets received, 0.0% packet loss
Success! The VM is reachable. Another way to get the VM’s IP addresses, is to run:
vagrant ssh -c "hostname -I"
Note that both addresses are returned:
Maintaining a hosts file
vagrant plugin install vagrant-hostmanager
Vagrantfile, set ubuntu.example.com as the host name and configure the hostmanager plugin. See comments below for reference.
Vagrant.configure(2) do |config| # Host name of the VM config.vm.hostname = "ubuntu.example.com" # Base image for VM config.vm.box = "ubuntu/precise64" # Configure private network by DHCP config.vm.network "private_network", type: "dhcp" # Update /etc/hosts on all active VMs config.hostmanager.enabled = true # Update host machine's /etc/hosts config.hostmanager.manage_host = true # Don't ignore private IPs config.hostmanager.ignore_private_ip = false # Include offline VMs (rather than just active ones) config.hostmanager.include_offline = true # Use IP resolver to get DHCP configured address config.hostmanager.ip_resolver = proc do |vm, resolving_vm| `vagrant ssh -c "hostname -I"`.split.last end end
To update the hosts file, destroy the machine and rebuild it. You will likely be prompted for your password so that hostmanager can update your hosts file.
vagrant destroy vagrant up
Now examine the tail of the hosts file:
## vagrant-hostmanager-start id: 1d6b1120-e125-4d26-82a8-559208175336 172.28.128.9 ubuntu.example.com ## vagrant-hostmanager-end
This shows that ubuntu.example.com has been successfully associated with the DHCP-assigned IP address of the VM. You can even ping it.
ping -c 4 ubuntu.example.com
Configuring a LAMP server
Check what happens if you navigate to ubuntu.example.com in your browser.
The connection is refused because there is no webserver running. So let’s install Apache:
vagrant ssh -c "sudo apt-get -y install apache2"
Now the browser should show a more promising page:
Let’s install the other elements of LAMP. Below, the
DEBIAN_FRONTEND environment variable ensures that you are not prompted for anything during installation, including setting a MySQL root password. Security warning: With this method, the MySQL root password will be blank. This is handy for local development, but not desirable otherwise.
vagrant ssh -c "sudo DEBIAN_FRONTEND=noninteractive \ apt-get -y install libapache2-mod-php5 \ php5-mysql mysql-server"
Create a local
www directory containing a dummy PHP file.
mkdir www echo "<?php echo 'Hello from PHP!';" > www/index.php
Add the following to the
Vagrantfile (before the final end):
# Sync local www directory with /var/www on the VM config.vm.synced_folder "www", "/var/www", owner: "www-data"
This simply mounts our local www directory where Apache looks for the default website. Note that the owner of the directory (within the context of the VM) will be www-data, the Apache user. Run reload to effect the changes:
Now in the browser, instead of “It works!”, you should see the greeting from PHP.
Next, modify PHP script and observe that the change immediately appears in the browser (upon refresh) because the file is synced to
/var/www on the VM.
echo "<?php phpinfo();" > www/index.php
Finally, download the latest 6.x version of Drupal. Extract into
www, overwriting the test
index.php file. The browser should show the standard Drupal installation screen.
If you want to proceed with the installation, you’ll need to create a database:
MY_SQL_COMMAND="CREATE DATABASE drupal; \ GRANT ALL ON drupal.* TO drupal@localhost \ IDENTIFIED BY 'password';" vagrant ssh -c "mysql -u root -e \"$MY_SQL_COMMAND\""
This creates a MySQL user called
drupal and grants it all permissions on database called
drupal, authenticated with an insecure password
password. If you want to, you can proceed with the instructions in the browser to complete the setup of Drupal.
Refactoring into a provisioning script
In the same directory as the
Vagrantfile, create a new file called provision, containing the below:
#! /bin/bash # Exit with failure immediately if any command fails set -e # Install Apache sudo apt-get -y install apache2 # Install PHP and MySQL with Apache bindings sudo DEBIAN_FRONTEND=noninteractive \ apt-get -y install libapache2-mod-php5 \ php5-mysql mysql-server # Create a dummy Drupal database MY_SQL_COMMAND="CREATE DATABASE drupal; \ GRANT ALL ON drupal.* TO drupal@localhost \ IDENTIFIED BY 'password';" mysql -u root -e "$MY_SQL_COMMAND" # Restart Apache /etc/init.d/apache2 restart
Add the following line to the
Vagrantfile, to instruct Vagrant to execute the provision script on the VM during provisioning.
# Use shell script for provisioning config.vm.provision "shell", inline: File.read('provision'), privileged: false
(Note that file: can be used instead of inline:, as per the shell provisioning documentation, but the latter makes debugging easier, since it forces an update with every
vagrant reload --provision.)
Now, for the moment of truth, destroy and recreate your entire VM with just two commands:
vagrant destroy vagrant up
For reference, this is the final
# vi: set ft=ruby : Vagrant.configure(2) do |config| # Host name of the VM config.vm.hostname = "ubuntu.example.com" # Base image for VM config.vm.box = "ubuntu/precise64" # Configure private network by DHCP config.vm.network "private_network", type: "dhcp" # Update /etc/hosts on all active VMs config.hostmanager.enabled = true # Update host machine's /etc/hosts config.hostmanager.manage_host = true # Don't ignore private IPs config.hostmanager.ignore_private_ip = false # Include offline VMs (rather than just active ones) config.hostmanager.include_offline = true # Use IP resolver to get DHCP configured address config.hostmanager.ip_resolver = proc do |vm, resolving_vm| `vagrant ssh -c "hostname -I"`.split.last end # Sync local www directory with /var/www on the VM config.vm.synced_folder "www", "/var/www", owner: "www-data" # Use shell script for provisioning config.vm.provision "shell", inline: File.read('provision'), privileged: false end
# vi: set ft=ruby : Vagrant.configure('2') do |config| # Supply name and URL for box config.vm.box = 'win81' config.vm.box_url = 'http://aka.ms/vagrant-win81-ie11' # Enable GUI config.vm.provider "virtualbox" do |vm| vm.gui = true end end
Suffice to say that since Mitchell’s first Vagrant commmit in 2010, the project has very quickly become an indispensable tool in any developer’s tool belt. The project’s activity remains hot, so we can expect more great things to come.