Vagrant: VMs Aren’t Dead Yet
Everyone is so excited about containers and Docker and Kubernetes. And with good reason, these technologies really turn development and operations on their heads, allowing for much greater ease of deployments and service reliability. Why load up an entire operating system in a virtual machine when you can pack all your code and dependencies into a portable, handy dandy container?
Well don’t count out VMs yet, they still definitely have their place. Maybe you still need to support traditional monolithic applications that need their own OS to run upon. Maybe you need some different OSes handy to test how a web app runs in Microsoft’s Edge vs. Apple’s Safari. Or maybe your environment is already entrenched in one hypervisor or another, like VMware’s ESX. Those licenses weren’t cheap, and it’s going to take a lot of convincing to switch at this point.
When working with VMs, the need to spin up machines for testing constantly arises. You could spin up some on your production VM hosts, but test, dev, and prod sitting close together makes most managers nervous. You can start some machines on AWS or another cloud provider, but then you incur some hourly costs. Sometimes it’s just easiest to run test machines on your local machine. No mingling with prod, no extra costs, just build, start, stop, and destroy machines whenever you need them.
If you find ever yourself in this situation, there is a tool you must add to your tool belt today: Vagrant. No more downloading huge ISOs or waiting on OS installations. Vagrant makes setting up test VMs as easy as two commands. It’s great at creating these disposable environments for testing code, infrastructure automation scripts, for designers working on web apps, and anyone that needs a quick VM or any reason. These machines are defined as config files, so they can be easily shared. Let’s check out getting started with Vagrant.
Your First Vagrant VM
Vagrant is a free tool created by Hashicorp, the makers of other infrastructure apps like Terraform and Nomad. It’s available for Windows, Mac, and Linux, and while it’s not a hypervisor itself for running VMs, it ties into a hypervisor you might already have installed.
Installation is easy, just grab the latest version for your OS from the downloads site. Next you need a hypervisor. Virtualbox is the standard and will be supported by Vagrant by default without additional setup, but other common supported options include Hyper-V and VMware Fusion (for macOS) or Workstation (for Windows).
Once everything is installed you’ll need a box. A Vagrant box is basically an image of a preinstalled VM. The most common source of boxes is the public catalog. You’ll find fresh installs of many versions of Windows and Linux, those same OSes with certain applications preinstalled (like a LAMP stack or Puppet agents), and niche distos like Kali Linux. We’ll use “ubuntu/bionic64” to start with, it’s a build of Ubuntu 18.04 that is maintained and uploaded to the catalog by Canonical.
To start your box, open a terminal or command line window, create a new directory and change into it, then run:
vagrant init ubuntu/bionic64
After a few seconds you’ll see a message that a Vagrantfile has been created and that you’re ready to start the VM. You can look at this file, it’s pretty long and is formed mostly of comments, we’ll come back to it. For now, run this:
Lots of things will happen, you should see Vagrant download the box first, configure networking and a bunch of other stuff, then hopefully end without any errors. This is a good time to open VirtualBox, you should see the VM running with a name like “default_123456.” Congrats, you just started a VM from nothing with two commands! No ISOs, no clicking next again and again through a long setup process, no waiting on files to copy, just magic.
More Magic: vagrant ssh
The magic is just getting started however. Run "vagrant ssh" and you should be magically SSH’d into the machine without knowing the password, without copying over an SSH key, and without even knowing the VM’s IP. This works because any "vagrant" command (besides init) will always assume that you are in a directory with a Vagrantfile and that any subcommands (like ssh in this case) are to be executed upon the machine that was built from that Vagrantfile. Change into a different directory and run "vagrant ssh" and it will fail because there is no Vagrantfile. This also makes simultaneously running multiple VMs from multiple Vagrantfiles easier to manage, they just all need to be in separate directories.
Back to SSH, how then did it connect without a password or key? Well look back up in all the text that scrolled by while is was starting, you should see:
default: Vagrant insecure key detected. Vagrant will automatically replace default: this with a newly generated keypair for better security. default: default: Inserting generated public key within guest… default: Removing insecure key from the guest if it’s present… default: Key inserted! Disconnecting and reconnecting using new SSH key…
Translation: Vagrant installs its own SSH key which is then referenced for the life of the VM, so running "vagrant ssh" will by default connect using that key.
Vagrantfile Options: config, network, box
Let’s go back to that Vagrantfile, open it up and take a look. There’s a lot in the file, but don’t be intimidated! All it but two lines are commented out with a #, all those commented out lines are different options for the VM, things like network settings, forwarding ports from localhost to the guest, and the amount of RAM allocated to the VM.
Of the two lines that are not commented out to start with, this one:
Vagrant.configure(“2”) do |config|
tells Vagrant that we’re doing a version 2 configuration. Version 1 would be used for backward compatibility with older versions of Vagrant. The other uncommented line sets the Vagrant box:
config.vm.box = “ubuntu/bionic64”
For example, suppose you want to create a number of VMs and put them all on a private network with a specific IP address range. Find and uncomment this line (by deleting the #):
config.vm.network “private_network”, ip: “192.168.33.10”
Save your change to the Vagrantfile and run vagrant reload. This command takes the existing VM and reboots it, applying any configuration changes without having to destroy and rebuild it.
Another option is changing the amount of RAM allocated to the VM. Find and uncomment these three lines and set the amount of RAM to your preference in megabytes:
config.vm.provider “virtualbox” do |vb| vb.memory = “1024” end
This first line starts a block of config specific to Virtualbox. The next line sets the amount of RAM Virtualbox will use, the “end” marks the end of this block of configuration. A quick vagrant reload and your VM will reboot with the new amount of RAM. Of course you could set all of these options before your initial vagrant up to set them upon the initial build.
One final helpful line you can manually add will set a hostname, otherwise the VM will be called “default” by, well, default. Add this at the very end of the final, before the last “end,” setting the hostname to whatever you’d like:
config.vm.hostname = “my_ubuntu_vm”
Another vagrant reload should bring your VM up with the hostname set. Now run vagrant destroy to stop and delete your VM to finish this exercise. Cleaning after yourself is always a good idea so you don’t end up with a bunch of VMs running on your machine, hogging your RAM and CPU. If you wanted to stop a VM without deleting it, you can vagrant halt. To list all your VMs and their status, try vagrant status.
Vagrantfiles with Multiple VMs
Two fun bits of info about Vangrantfiles that will make them much more manageable and flexible: you can delete all the commented lines from the default Vagrantfile down to the few you need and multiple VMs can be defined and deployed from a single file. Here’s an example of two Ubuntu VMs, with all the comments removed, and networking and RAM configured:
Vagrant.configure(“2”) do |config| config.vm.box = “ubuntu/bionic64” config.vm.network “private_network”, ip: “192.168.50.2” config.vm.provider “virtualbox” do |vb| vb.memory = “2048” end config.vm.hostname = “ubuntu1” end Vagrant.configure(“2”) do |config| config.vm.box = “ubuntu/bionic64” config.vm.network “private_network”, ip: “192.168.50.2” config.vm.provider “virtualbox” do |vb| vb.memory = “1024” end config.vm.hostname = “ubuntu2” end
Copy and paste this to your favorite text editor, save it as Vagrantfile, then bring the VMs up. While you’re waiting on them, open Virtualbox and you should see the machines. Don’t forget, Vagrant isn’t a hypervisor, it’s just sending commands and configuration to a hypervisor, Virtualbox by default.
You’re Just Getting Started
That’s all for now but we’re only scratching the surface of what Vagrant can do. Want to install cloud VMs instead of using Virtualbox? Check out vagrant-aws, a provider that will automate deploying EC2 instances into AWS with Vagrant.
Speaking of provisioners, there are several other built-in ones which can add superpowers to your Vagrantfiles. They can enable features like executing shell scripts after the VM is up or running automation routines from tools like Ansible, Salt, or Puppet. Any of these tools have their own config files which the Vagrantfiles calls to run after the VM is up. They can install packages, copy over files, start services, etc., creating fully formed VMs, all deployed with code.
The VMs as code concept is another important concept born from Vagrant. You can easily share Vagrantfiles, which makes recreating exactly identical environments across teams super easy. Then take it to the next step and check in these files into a Git repo to gain all the advantages therein like collaboration and version tracking.
So go build some VMs, learn some Linux commands, install and configure a web server, build a Windows domain home lab, go nuts! And thanks to Vagrant, the process will be so much smoother, faster, and more reproducible.
delivered to your inbox.