Introduction

Vagrant was first released in 2010 by Mitchell Hashimoto who later went on to create HashiCorp - a company that also developed tools such as Terraform, Vault and Packer. It can be described as a wrapper around virtualization software like VirtualBox, which helps to manage virtual machines with CLI and creating their configurations as code. Docker, on the other hand, uses Linux kernel features to create and run containers - packaged application instances, which are isolated from the rest of the system and then can also be managed with terminal commands. Thanks to these functionalities, both tools can be used to set up portable and uniform development environments eliminating the "works on my machine" problem (Docker advertises itself as a solution to this issue in one of its use cases, while Vagrant was specifically created for this). With that in mind, why would you use Vagrant, which requires a full-fledged virtual machine instead of lightweight containers?

A case for Vagrant

Here I will provide a few advantages that Vagrant has over Docker when using these tools as development environments. Let's start by showing a visualisation of architectures of both solutions.

VMs vs containers

Let's also compare ways of quickly starting a VM with Vagrant and a container with Docker and entering their shells.

# Starting a CentOS 8 virtual machine
$ vagrant init centos/8
$ vagrant up
$ vagrant ssh
# Starting a CentOS 8 container
$ docker run -it centos:8 bash

As you can see Docker requires fewer commands. However, a multitude of option flags for configuring port binding, limiting memory or CPU count among others needs to be remembered. Similar things can be configured in Vagrant with code in a special file named Vagrantfile.

More resource-heavy than Docker?

Because Vagrant uses existing virtualization software under the hood (it can work on top of VirtualBox, VMWare and Hyper-V among others), it seems that Docker has an advantage in being more lightweight as it uses Linux kernel features instead of hypervisors. But if it requires Linux, how come Docker also runs on Windows and macOS? As it turns out, Docker Desktop for Windows and for Mac use virtualization (Hyper-V and HyperKit respectively) to run the Linux kernel, so it makes Docker lose its lightweight appeal, especially considering the fact that, according to Stack Overflow Developer Survey from 2019, only around 25% of developers use Linux. Another disadvantage that comes from this approach is that the Windows version requires enabling Hyper-V, which means that you can't currently run any other virtualization software. The solution to this would be using legacy Docker Toolbox, which uses VirtualBox instead of Hyper-V. However, you may feel this becomes really confusing really quickly when you just want to start development. So, if you want to run Docker on Windows or macOS the architecture will look like this:

Docker on Windows and Mac

Vagrant is available on all major platforms i.e. Windows, macOS and Linux, and its installation is fairly straightforward. However, it requires us to have a hypervisor already installed - VirtualBox is probably the best option as it's free, open-source, well supported and runs on all previously mentioned systems.

Running development environments in the cloud

Another advantage of Vagrant over Docker is Vagrant's ability to create and manage virtual machines in the cloud on platforms such as AWS or DigitalOcean via plugins. It helps to off-load developers' machines when working with resource-heavy applications. When we add IDEs and other software required for daily work, it's clear that the memory limit can be easily reached when working with a limited amount of RAM, especially when more than one instance of the application is required for development. However, it requires taking care of security measures such as making the machines unavailable from public networks with services like Virtual Private Cloud. It can also simplify the process of sharing the environment when troubleshooting, for example. There are dedicated services for running Docker containers in the cloud such as Amazon Container Service. However, it is suited more towards production use and configuring and managing it through API or web interface isn't as convinient as a Vagrantfile.

Closer resemblance to the production environment

As mentioned previously Docker containers run applications in a consistent environment. To further explain, these environments are based on existing Linux distributions e.g. Debian, CentOS, Alpine and more. However, this doesn't quite mean that everything can be done in a container in a way it would be on a full OS. This manifests in multiple ways: from little inconveniences like containers usually not including crontab by default, which is a standard Unix utility, to things that are virtually impossible to achieve such as testing systemd unit files, because Docker containers use a different, minimal init process. While systemd has been a de facto standard Linux init process for a few years now, it is understandable that Docker doesn't want to use a complex service manager such as systemd, but it shows that Docker containers aren't a replacement for VMs, unless the application was designed with containers in mind. What is more, because Vagrant operates on VMs, it can run other operating systems.

More provisioning options

Vagrant provides out-of-the-box support for a number of provisioning software such as Chef or Ansible. It makes installing all dependencies and software required to run your application in a uniform way and is very convenient when a provisioner is already used to configure other environments. Technically, you could manually install Chef or similar software on Docker, but it seems like too much of a hassle to do it that way, when integrating Chef cookbooks in a Vagrantfile is a matter of a few lines of code. The only way by default to provision a Docker container is with shell commands (which is also possible in Vagrant) and in case of a change the image needs to be rebuild from scratch. The advantage of Vagrant with tools such as Chef is that a VM can be reprovisioned in an idempotent way, which means that only changes are applied (as opposed to configuring everything all over again).

The ugly sides

Despite all the positive sides of Vagrant, there are also some negative ones as well. One of the more inconvenient downsides is not being able to manage virtual machines globally. For example, to create a second instance of an already running VM, you have to copy the Vagrantfile to a new directory. The same goes for any other operation on a VM - you need to be in a directory where the Vagrantfile of a specific machine is located. Another option would be creating a multi-machine configuration, which lets us define multiple machines in one Vagrantfile, but that doesn't quite match Docker's CLI capabilities. Overall, the networking experience seems to be better in Docker - the containers are in the same network by default, while the same behaviour needs to be configured manually in Vagrant. Another advantage of Docker is caching - when an image has to be rebuilt, it actually only rebuilds the part that has changed thanks to Docker's layer caching. Rebuilding a Vagrant machine can be a painfully long process as it doesn't have any caching mechanisms and plugins that did add such functionality seem to be abandoned. An alternative would be to create an image containing all required software with another HashiCorp tool - Packer, but that obviously generates additional work. Quite a few problems come down to specific hypervisors like VirtualBox, so I won't be touching on them.

Summary

As shown, there are quite a few reasons that Vagrant can be a better tool than Docker for creating development environments. If the application isn't made with containers in mind, Vagrant proves to be better at resembling the production environment with perks like using provisioning software. It also isn't necessarily much more demanding than Docker, particularly considering the fact that it can run virtual machines in the cloud. However, it should be noted that Docker's benefits are more than just creating development environments, which I didn't mention in this article. You can read about them in articles like top 10 benefits of using Docker. Let's also remember that Vagrant is not an ideal solution as it has its own share of weaknesses such as no global management or caching mechanisms.