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
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:
To shut down the VM, type
vagrant suspend. To boot again, type
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
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
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.
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.
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
We could actually go one step further to use a virtual hostname.
Edit your local
$ sudo nano /etc/hosts
Add the following line at the end and save.
You could now access the page with
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.
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.
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
Let's verify this by logging into the VM and list the files under
$ 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 ssh vagrant@ubuntu-xenial:~$ sudo rm -rf /var/www vagrant@ubuntu-xenial:~$ sudo ln -fs /vagrant/www /var/www vagrant@ubuntu-xenial:~$ logout
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
./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.
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
#!/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
/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
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
- The script
- The folder
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
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
Both will show the same page, running on two different VM.
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
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>.
$ vagrant destroy 1ba11a8
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.