Why Vagrant is a godsend for full-stack developers

If you are or want to be a full-stack developer, but have yet to try out Vagrant, you should check it out now.

In this post, I want to share with you a typical journey of a full-stack developer, from amateur to professional. You will see why Vagrant is a godsend for full-stack developers.

Then, I will end by sharing some tips on getting started with Vagrant for local development.

Working directly on the production server

Most amateur full-stack developers started off working directly on the production server. For example, write code locally, upload to the server via FTP, or even edit code directly on the server.

... And pray that your website won't crash.

... But eventually, either it crashed or you started developing a phobia for uploading code.

Working on a remote staging server

To overcome the above problem, you started setting up a remote staging server that mirrors the production server.

You could now test your code on the staging server before uploading it to production. You finally got rid of the code-uploading phobia.

... But eventually, you started getting impatient. You realised you spent a lot of times waiting for code uploading.

Working locally, but on a different OS

You started looking for a solution to develop and test locally.

Chances are... Your server runs on Linux, and your local machine runs on Windows or macOS.

You tried your very best to mirror the environment, and it worked... at least for a start.

... But eventually, you faced the following problems:

  • This worked on my machine, but crashed on the staging/production server.
  • It took days to onboard a new developer, as you need to install many dependencies to mirror the production server.
  • You switched between different versions of the same software, e.g. a project need PHP 5.6 and another need PHP 7.2.

Vagrant, the godsend solution

Vagrant is the solution to the above problem. It is a tool for building and managing virtual machine (VM) environments, built on top of mature technologies like VirtualBox, VMWare, AWS and any other provider.

Imagine you can provision a Linux VM that mirror your production in your local machine with just a few commands. You can edit your file locally and the changes are hot-loaded into the VM directly.

Or you want to test a new software. You can easily launch a VM, experiment with the setup, mess around the OS. Once you are done, just destroy the VM. No more messing with your own OS.

Getting started with Vagrant

To get started, install VirtualBox and Vagrant.

Open Terminal (or Command Prompt for Windows).

Let's create a folder for our project:

$ mkdir my-project
$ cd my-project

Then just type the following:

$ vagrant init ubuntu/xenial64
$ vagrant up

For the first run, it will take a while as it needs to download the virtual image and do some configurations.

Once the command finished, believe it or not... a clean Ubuntu 16.04 LTS (Xenial Xerus) virtual machine is already running in the background.

vagrant init ubuntu/xenial64 initialized your project folder by creating Vagrantfile. This file marks the root directory of your project and contains configuration that describes the project environment. ubuntu/xenial64 is the Vagrant Box we use for the project. A Vagrant Box is a base image of a virtual machine. In this case, it is an official image of Ubuntu 16.04 LTS (Xenial Xerus). You can discover other Vagrant Boxes at Vagrant Cloud.

To log into the shell of the VM, just type:

$ vagrant ssh

This will drop you into a full-fledged SSH session. To terminate the SSH session, just type:

vagrant@ubuntu-xenial:~$ logout

To shut down the VM, type vagrant suspend. To boot again, type vagrant up.

To demo that we can actually set up a website in the VM, let's install Apache in it.

$ vagrant ssh
vagrant@ubuntu-xenial:~$ sudo apt update
vagrant@ubuntu-xenial:~$ sudo apt -y upgrade
vagrant@ubuntu-xenial:~$ sudo apt -y install apache2

After installation, make sure that Apache is running in the VM:

vagrant@ubuntu-xenial:~$ curl -I http://localhost/
HTTP/1.1 200 OK
Date: Tue, 20 Nov 2018 03:02:58 GMT
Server: Apache/2.4.18 (Ubuntu)
Last-Modified: Tue, 20 Nov 2018 03:01:58 GMT
ETag: "2c39-57b0fdb5cb6ed"
Accept-Ranges: bytes
Content-Length: 11321
Vary: Accept-Encoding
Content-Type: text/html

Vagrant's Networking

The above shows that Apache is running in the VM. However, to access it from our local machine, we need to expose the service. There are 3 ways to do so.

The first way is via port forwarding.

Logout and edit the Vagrantfile as follow. I will use nano but you could use any plain-text editor of your choice.

vagrant@ubuntu-xenial:~$ logout
$ nano Vagrantfile

Uncomment the following line by removing # and save.

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

This basically tells Vagrant to expose port 80 of the guest machine (i.e the VM) and forward port 8080 of the host machine (i.e. your local machine) to port 80 of the guest. In another term, you can access Apache in the VM (which listen to port 80) on your computer with http://localhost:8080/.

Reload the VM with the new configuration.

$ vagrant reload

Now, try to access http://localhost:8080/ in the browser of your computer. You should see the Apache2 Ubuntu Default Page.

It works!

Most tutorials will stop at the first way. Personally, I prefer the second way, which is creating a private network and allows the host to access the VM using a specific IP.

Edit Vagrantfile again. Comment out the previous line. And uncomment another line shown below.

...
# config.vm.network "forwarded_port", guest: 80, host: 8080
...
config.vm.network "private_network", ip: "192.168.33.10"
...

Reload the machine again with vagrant reload. You can now access the page with http://192.168.33.10/

We could actually go one step further to use a virtual hostname.

Edit your local hosts file.

$ sudo nano /etc/hosts

Add the following line at the end and save.

192.168.33.10 www.my-project.com

You could now access the page with http://www.my-project.com/

The above only allow you to access the VM on your local computer, as it creates a private network.

If you are developing on multiple devices - e.g. run API with Vagrant on your computer, run an app on the mobile phone to access the API. You will need the third solution - create a public network, which generally matched to a bridged network.

Edit Vagrantfile again. Commment out the private network line, and uncomment the public network line as follow:

...
# config.vm.network "private_network", ip: "192.168.33.10"
...
config.vm.network "public_network"
...

Then reload the VM with vagrant reload. This time, it will prompt you to choose a bridged network:

==> default: Available bridged network interfaces:
1) en1: Wi-Fi (AirPort)
2) en0: Ethernet
3) p2p0
4) en2: Thunderbolt 1
5) bridge0
==> default: When choosing an interface, it is usually the one that is
==> default: being used to connect to the internet.
    default: Which interface should the network bridge to? 

Choose the one that you use to connect to the Internet. For me, it is my Wi-Fi. This makes the VM appears as another physical device on your network.

To find its IP. Run the following:

$ vagrant ssh -c "ifconfig"

It will list a few networks and IPs. For a local network, it usually starts with 192.168.x.y.

In my case, it is the following network.

enp0s8    Link encap:Ethernet  HWaddr 08:00:27:26:de:e1  
          inet addr:192.168.1.5  Bcast:192.168.1.255  Mask:255.255.255.0
          inet6 addr: fd11:493:4077:1:a00:27ff:fe26:dee1/64 Scope:Global
          inet6 addr: fe80::a00:27ff:fe26:dee1/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:61 errors:0 dropped:0 overruns:0 frame:0
          TX packets:33 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:6140 (6.1 KB)  TX bytes:3814 (3.8 KB)

You can now access the page on http://192.168.1.5/ on your local computer and your mobile device within your local network.

Synced Folders

Launching VM in a few commands is cool. But it would be painful if you need to edit files in the VM via terminal.

The answer to this is synced folders.

By default, Vagrant will mount your project root directory to the /vagrant directory in the VM. Note that this is different from the /home/vagrant directory when you log in with vagrant ssh.

Let's verify this by logging into the VM and list the files under /vagrant.

$ ls
Vagrantfile  ubuntu-xenial-16.04-cloudimg-console.log
$ vagrant ssh
vagrant@ubuntu-xenial:~$ cd /vagrant
vagrant@ubuntu-xenial:/vagrant$ ls
ubuntu-xenial-16.04-cloudimg-console.log  Vagrantfile

As shown, the /vagrant directory contains the same files as the project root directly.

Let's create a folder in the VM and show that it will appear on your local computer.

vagrant@ubuntu-xenial:/vagrant$ mkdir www
vagrant@ubuntu-xenial:/vagrant$ logout
Connection to 127.0.0.1 closed.
$ ls
Vagrantfile  ubuntu-xenial-16.04-cloudimg-console.log  www

Hot Loading Code

Now is time to revisit one of our original objectives for setting up a local development environment. It is so that we could edit codes in our favourite editor, and see changes directly on our local browser.

This can be achieved by symbolic linking of /vagrant/www to /var/www.

$ vagrant ssh
vagrant@ubuntu-xenial:~$ sudo rm -rf /var/www
vagrant@ubuntu-xenial:~$ sudo ln -fs /vagrant/www /var/www
vagrant@ubuntu-xenial:~$ logout

Try visit http://192.168.1.5/, you would be greeted by Not Found page as our project folder ./www is empty.

Now, use your favourite editor, create html folder under www folder of the project. Then create index.html under ./www/html with the following HTML code.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Vagrant is a godsend</title>
  </head>
  <body>
      Vagrant is a godsend
  </body>
</html>

Try refreshing your browser and you should see the HTML page now!

This is because the default DocumentRoot of Apache in our VM is /var/www/html. So the HTML files we just created in the local computer as ./www/html/index.html will automatically sync as /var/www/html/index.html in the VM.

Automated Provisioning

So far, we have set up everything in a rather manual way. To replicate the setup, we would have to repeat the same steps. A better solution is to use automated provisioning. In short, we write a shell script and configure Vagrant to run this script when setting up the VM.

Create the following shell script and save it as bootstrap.sh in the same directory as Vagrantfile:

#!/usr/bin/env bash

apt update
apt -y upgrade
apt -y install apache2
if ! [ -L /var/www ]; then
  rm -rf /var/www
  ln -fs /vagrant/www /var/www
fi

Note that the steps are similar to what we run manually earlier. The difference is we no need sudo as the script will be executed as root user. Also, we run the steps to remove /var/www and sym-link /vagrant/www to /var/www only if /var/www is not already a symbolic link. This prevents the script from accidentally delete /vagrant/www if it was run twice for whatever reason.

Make it executable:

$ chmod a+x bootstrap.sh

Next, we configure Vagrant to run this script when setting up the machine. Edit Vagrantfile and add the following line before end:

  config.vm.provision :shell, path: "bootstrap.sh"
end

With this, it is now ready to share your Vagrant environment so that anyone can set up the same environment in a few commands.

Share your Vagrant environment

To clone your environment above, one only needs 3 things:

  • The file Vagrantfile
  • The script bootstrap.sh
  • The folder www

One of the best ways to share this is to commit them into a Git repo. So anyone who wishes to replicate the environment can just checkout the files, and run vagrant up.

If you are new to Git, that will need another post to introduce it.

For now, let's demonstrate the sharing by simple copying of files.

$ cd
$ mkdir my-cloned-project
$ cp my-project/Vagrantfile my-cloned-project/
$ cp my-project/bootstrap.sh my-cloned-project/
$ cp -r my-project/www my-cloned-project/
$ cd my-cloned-project/
$ vagrant up
# vagrant ssh -c "ifconfig"

If you have not suspended the previous VM. You should get a different IP for the new VM. In my case, it is 192.168.1.10 this time.

Now, try load http://192.168.1.10/ in the browser.
Also, try loading the previous link http://192.168.1.5/.
Both will show the same page, running on two different VM.

Cleaning Up

If you fired up a lot of Vagrant projects, you may notice that you are running out of local disk space very soon.

When you run vagrant up for the first time, Vagrant downloads the required base box and save it on your local computer. e.g. In macOS, it is saved under ~/.vagrant.d/boxes/. It then uses the base box to create the image of the VM. The VM image is saved under ~/VirtualBox VMs/.

Base boxes are re-usable for other projects, but every project will have their own VM image. Any file changes you make in the VM that is not in the synced folder is actually persisted in the VM image.

That is why you can run vagrant suspend and then vagrant up to continue from where you stop.

At times, you may want to destroy a VM image. This can be done by running vagrant destroy in the project directory.

Let's destroy the cloned project.

$ cd ~/my-cloned-project
$ vagrant suspend
$ vagrant destroy

This will delete the VM image from your computer. If you run vagrant up again, Vagrant will provision a new machine.

Sometimes, you may want to show all VMs available in your computer as follow:

$ vagrant global-status
id       name    provider   state   directory                           
------------------------------------------------------------------------
0872de7  default virtualbox saved   /Users/boonkgim/tech/ubuntu         
4483037  default virtualbox saved   /Users/boonkgim/bk-blog             
5e829dc  default virtualbox saved   /Users/boonkgim/boonkgim.com        
cb3dc3a  default virtualbox running /Users/boonkgim/next.js             
1ba11a8  default virtualbox saved   /Users/boonkgim/my-project          
 
The above shows information about all known Vagrant environments
on this machine. This data is cached and may not be completely
up-to-date. To interact with any of the machines, you can go to
that directory and run Vagrant, or you can use the ID directly
with Vagrant commands from any directory. For example:
"vagrant destroy 1a2b3c4d"

You could destroy the VM without going into the project directory with vagrant destroy <id>.

Let's destroy my-project too:

$ vagrant destroy 1ba11a8

What's Next?

This post only introduces you to the basic features of Vagrant. There are advanced topics like customising synced folders, using other providers such as AWS, provisioning with Puppet, Chef, or Ansible, and etc.

I suggest you go through the official Getting Started guide and Vagrant Documentation to master it.

Subscribe to get articles about anything tech and startup.