osteel's blog Web development resources

How to use Vagrant for local web development

"Vagrant logo"

This article shows how to quickly get up and running with Vagrant, to create and use local Virtual Machines as development environments, all with a single command. This is indeed written from a web developer's standing point, and I will not spend too much time describing how things work under the hood (not that I am an expert anyway).

The point of Vagrant is precisely not to have to worry too much about it.

The final result of this tutorial is available as a Github repository. In case of trouble, don't hesitate to refer to it.

Summary

Vagrant?

Vagrant greatly simplifies the use of Virtual Machines to spawn development environments in no time (well, it's probably more like no effort than time).

To understand what a Virtual Machine (VM) is, think of an emulator: you install it on your computer so you can then run software that believe they are running in the environment they were designed for. All inside your own machine (which is then called the host).

That is essentially what a VM is.

Vagrant is a VM manager, in the sense that it reduces the management and the configuration of VMs to a handful of commands.

It relies on a VM provider, that deals with virtualization itself. As its support is shipped with Vagrant, we will use VirtualBox, but others exist.

So what is actually the point? The main argument is the consistency of the environments among developers working on the same project, and more importantly that these environments reflect the production ones. Ship a Vagrant configuration with each project, and every developer will work on the same environment locally.
No surprises when pushing the code live, no more "it works on my machine".

The other advantages I see is the fact that one can still use their editor of choice, as Vagrant folders are shared with the host by default. It also permits not to clutter up your computer with tons of different libraries and software that aren't used by every project.
If something goes wrong, you don't screw your machine: you destroy your VM instance and recreate it instead.

Easy. And safe.

Basic installation

First, download VirtualBox at https://www.virtualbox.org/wiki/Downloads ("platform packages") and install it.
Then, download Vagrant at https://www.vagrantup.com/downloads.html (v1.7.2 at the time of writing), install.

Open up a terminal and type:

vagrant version

The version should be displayed. If not, log out your session, log back in and try again.

Picking a box

Vagrant's documentation uses a Ubuntu 12.04 LTS 32-bit server box. Now let's say you want Ubuntu 14.0 LTS 32-bit: go to the catalog of Vagrant boxes and type "Ubuntu 14.04" in the search field.
Spot the Ubuntu one in the list: "ubuntu/trusty32".

Now get back to your terminal, browse to the directory you want your project to reside in, and type this:

vagrant init ubuntu/trusty32

The terminal should display something like:

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 about using Vagrant.

Good. Now type:

vagrant up

This is how you start your Virtual Machine. As you picked Ubuntu 14.04, Vagrant will try to install it: as it won't find a corresponding box on your computer, it will fetch it directly from the catalog (this can take a while according to your connection speed), and boot it once the download is over.

All you have to do now is:

vagrant ssh

BOOM! You are now in your Ubuntu 14.04 server.

Note for Windows users: the vagrant ssh command might not work for you if SSH is not in your PATH variable. I invite you to take a look at this separate article and come back.

What exactly happened here? vagrant init created a Vagrantfile file in the directory. This file contains the various settings Vagrant needs to spawn a VM. You can have a look at it now, it is basically full of commented examples (don't freak out, it is not as bad as it looks).

You will find one uncommented line tho:

config.vm.box = "ubuntu/trusty32"

Yeah, you got it: as we invoked vagrant init followed by the box we wanted to use, Vagrant created its config file with the corresponding setting.

While you are connected to your box, type this:

ls /vagrant

The Vagrantfile should be listed. This is the same Vagrantfile you have just updated: /vagrant is the folder that is shared between the host machine and the VM.

You can simply leave your box typing ctrl + d. I won't explain how to shutdown it; just take a couple of minutes to read about the different available options in the doc, they are well explained.
Just know that even if you destroy your box, files under /vagrant remain untouched.

Vagrantfile

I am not going to go through all the options here, only those I most often use.

The Vagrantfiles are written in Ruby, but no Ruby background is required (I don't know much about Ruby myself).

Let's look into accessing your VM from the host. Using your host machine's editor to update files on your VM via shared folders is nice, but at some point you will want to admire your work in a browser, which implies for it to have access to your Vagrant box somehow.

The easiest way to achieve this is probably using port-forwarding.

Port-forwarding

First let's run a quick test. Open a terminal on your host machine and type the following command:

telnet localhost 8080

Unless the port 8080 is already in use by something else (in which case change 8080 for whatever port you know is available), you should get something like:

Trying 127.0.0.1...
telnet: connect to address 127.0.0.1: Connection refused
Trying ::1...
telnet: connect to address ::1: Connection refused
telnet: Unable to connect to remote host

Edit your Vagrantfile and add the following:

config.vm.network :forwarded_port, guest: 80, host: 8080

Now reload your VM:

vagrant reload

or vagrant up if you had shut it down. Once it is booted, try the telnet command above again in the other terminal window. This is what you should read:

Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.

Port 8080 is now being listened to, and connections to it are forwarded to port 80 of the Vagrant box. This comes in handy when you use something like grunt-contrib-connect to quickly spawn a static HTTP server on a specific port. In our case, using port 80 in the Grunt config and accessing http://localhost:8080 from our host machine would display the website contained in the folder set with the base parameter.

This is nice for quick prototyping for example, as no further server configuration is required. In most cases however, as we want to replicate a production environment as accurately as possible, we will want to use a proper server config.

To do so, we are going to assign a private IP to our box.

Private IP

First, go back to the terminal and try:

ping 192.168.68.8

You should get request timeout responses. Edit the Vagrantfile again and add:

config.vm.network :private_network, ip: "192.168.68.8"

Reload your VM and try again: you should now get responses.

This is it for the options I mainly use; I rarely need the rest. Be curious and scan through the official documentation, it is well written and I am sure you will find something useful to you.

On a side note, and to emphasize the usefulness of Vagrantfiles, I think their beauty resides in the fact that all it takes to make the environment available to other developers is to version them with your projects. Whenever you clone a project containing a Vagrantfile to a machine with Vagrant installed on it, you are at a vagrant up away from having it running locally.

The rest of the process to get your website displayed using the private IP is covered in the next section.

Provisioning

Alright, so you've got your VM running, you can edit files from outside of it, and access it from the host machine using its private IP.

How to properly display your work in a browser now?

Let's cut to the chase here: it implies setting a server on your VM and matching its IP to the domain name you chose in the host machine's hosts file.

As our Vagrant box is almost empty at this point, we need to install a HTTP server on it. We will go for Nginx here, but instead of installing it and setting up the server from the VM itself, we are going to use provisioning.

Shell scripts

Provisioning is achieved from the Vagrantfile as well and, if different means are available to do so, I will only cover the most basic one for now, i.e. using shell scripts (I will mention the other ways later on).

Open the Vagrantfile in your editor, and add this:

config.vm.provision :shell, :path => ".provision/bootstrap.sh"

Basically what we tell Vagrant is "Use the shell script that you will find in .provision/bootstrap.sh to provision the box".

Don't reload your box just yet, as we need to create this file first. Add a new folder named ".provision" in your project (same level as the Vagranfile), and create a bootstrap.sh file in it. Here is the full content of this file:

#!/usr/bin/env bash

# nginx
sudo apt-get -y install nginx
sudo service nginx start

# set up nginx server
sudo cp /vagrant/.provision/nginx/nginx.conf /etc/nginx/sites-available/site.conf
sudo chmod 644 /etc/nginx/sites-available/site.conf
sudo ln -s /etc/nginx/sites-available/site.conf /etc/nginx/sites-enabled/site.conf
sudo service nginx restart

# clean /var/www
sudo rm -Rf /var/www

# symlink /var/www => /vagrant
ln -s /vagrant /var/www

Now, let's describe it step by step:

#!/usr/bin/env bash

We are basically telling where to look for the bash program.

# nginx
sudo apt-get -y install nginx
sudo service nginx start

Install Nginx and start it. The -y option allows to automatically answer "yes" where user input is normally required.

# set up nginx server
sudo cp /vagrant/.provision/nginx/nginx.conf /etc/nginx/sites-available/site.conf
sudo chmod 644 /etc/nginx/sites-available/site.conf
sudo ln -s /etc/nginx/sites-available/site.conf /etc/nginx/sites-enabled/site.conf
sudo service nginx restart

We copy the server configuration from .provision/nginx/nginx.conf (yet to be written at this point, we're getting there) to Nginx's sites-available folder, ensure the permissions are right, then create the symlink from sites-enabled/site.conf to sites-available/site.conf, and restart Nginx to take this new config into account.

Finally, we create a symbolic link from /var/www to /vagrant, after having removed the default files Nginx creates at installation:

# clean /var/www
sudo rm -Rf /var/www

# symlink /var/www => /vagrant
ln -s /vagrant /var/www

/var/www is often where the code goes on a server (even though /srv/www/ might be more relevant, but that's another debate). As mentioned earlier, on a Vagrant VM the shared folder is /vagrant. Using such a symlink allows to match your production server's settings (or staging or whatevs).
This is completely optional tho.

Server config

Almost there! If you are attentive, you know that we now need the Nginx server config. Under the .provision folder, create a new folder named "nginx" and open a new nginx.conf file in it. Here is its content:

server {
  listen 80;

  server_name vagrant-test.local.com;
  access_log /var/log/nginx/access.log;
  error_log /var/log/nginx/error.log;
  root   /var/www;

  location / {
  }
}

Nothing too esoteric here, this is a very basic Nginx server config. Just note that our server name will be "vagrant-test.local.com". Save your file, and reload the box using this command:

vagrant reload --provision

Normally, the provisioning is done only at the first boot of the VM. By specifying the --provision option, we force Vagrant to perform the provisioning again.

The start up will take slightly longer than previously, and you should see quite a lot more instructions on the screen: the packages listed in the bootstrap.sh file are being installed.

Matching the private IP

We only need two little extra steps now: adding the private IP and the server name to the host machine's hosts file and creating a simple index.html file in our box to be displayed in the browser later on. On Mac OS, edit the hosts file with the following command:

sudo vim /etc/hosts

On Windows, you will find it under Windows/System32/Drivers/etc/ (you will have to edit it in admin mode).

Add the following line:

192.168.68.8    vagrant-test.local.com

Save and quit.

Now add a little index.html file at your project's root:

<!DOCTYPE html>
<html>
    <head>
        <title>Vagrant test</title>
    </head>
    <body>
        <h1>Oh hi!</h1>
    </body>
</html>

Alright ladies and gentlemen, drumroll please: open your favorite browser and navigate to http://vagrant-test.local.com. You should see the HTML page you have just created \(^o^)/

Phew! That was a rocky ride, eh!

Well, yeah. But think about it this way: save this basic config somewhere (craft it to your needs, adding PHP or whatever floats your boat), and use it anytime you start a new project. You can now get up and running with a simple vagrant up command and a new line in the hosts file.

I don't know about you, but it sounds quite nice to me.

Tips

.gitignore

If you use Git, add ".vagrant/" to your .gitignore file. Vagrant creates this folder when booting the VM: it contains auto-generated stuff you don't want to version.

Handle port collisions

When using port-forwarding, it can happen that a Vagrant box uses a port that is not available. It will tell you about it anyway, but if the host machine's port doesn't really matter to you, amend the corresponding line in the Vagrantfile this way:

config.vm.network :forwarded_port, guest: 8080, host: 80, auto_correct: true

The auto_correct parameter will allow Vagrant to automatically assign another port of the host machine in case of unavailability (and tell you about it, of course).

Copy host git config

I usually try to avoid having too many terminal windows open at the same time. That's why I like to install Git on my VMs so I don't need another terminal window only to perform the versioning operations. This implies having your Git config inside the Vagrant box, which is actually quite easy to do automatically. In your Vagrantfile, add this:

config.vm.provision "file", source: "~/.gitconfig", destination: "~/.gitconfig"

At the first boot of the VM, your Git config file will be copied over.

GUI console

Occasionaly, you may have some trouble booting your VM. This can notably happen when your box wasn't properly shut down the last time you used it. The boot process might just hang there, and the reason may not be obvious. In that case, add the following lines to your Vagrantfile:

config.vm.provider :virtualbox do |vb|
  vb.gui = true
end

and try to boot again. What this does is it will open VirtualBox's GUI console, which displays what is happening behind the scenes, and thus should help you troubleshoot the issue.

Access the host machine when using a private network

Sometimes, you may need to access the host machine from the guest one. How to do so?

When you set up a private network, 192.168.68.8 in our case, the host machine automatically takes 192.168.68.1 as its private IP address.

What now?

Well, that is quite a lot to digest already. And yet this is just the beginning, there are many more features to explore.

Just to mention a couple of them tho:

Provisioning tools

In this tutorial, we used simple shell scripts. But it turns out Vagrant (supposedly) plays nicely with more advanced solutions such as Chef or Puppet.

Admitedly tho, I tried to use Puppet something like a year ago, and completely failed to get it working with Vagrant. I am yet to give Chef a try.

Vagrant Share

Local development is good, but soon you will be confronted with the need to expose your work to the world, whether it is to show some progress to a client or to allow an API to reach your application. This is achievable with nice tools such as ngrok (hopefully I will soon publish a tutorial about it), but Vagrant unveiled a new service a few months ago, called "Vagrant Share", that does just that.

I am yet to try it out (a tutorial would probably follow), but it definitely looks promising.

Posted by osteel on the :: [ vagrant tutorial webdevelopment environment ]

Comments