Creating and provisioning VMs with Vagrant and VirtualBox
Tags: it, devops, virtualization
Published: 2019-06-29 02:59:43

---
title: Creating and provisioning VMs with Vagrant and VirtualBox
published: true
series: DevOps
description: 
tags: IT, devops, virtualization
---
At my job, we've been having a lot of issues with students having misconfigured / incompatible (or annoying to set up) hardware. Dealing with PostgreSQL, MongoDB, Docker, and filesystem operations in a place where people are split between Windows and UNIX-like environments is a total pain, and WSL isn't fully mature, so I decided to prepare a VM that would allow them to develop MERN stack applications with minimal fuss. (it's [here](https://github.com/tttaaannnggg/webvm) if you want to check it out or give feedback!)

I used [Vagrant](https://www.vagrantup.com/) with [VirtualBox](https://www.virtualbox.org/), which I hear isn't ideal for heavy lifting- but hey, we'll use Docker if we need better performance.

The process of using Vagrant is pretty simple. You create a `Vagrantfile`, which defines the behavior of your VM. In my case, I want to use it for webdev, so I'm starting with an Ubuntu 18.04 installation (as opposed to a Windows one). I'm also forwarding ports 3000, 5432, 8000, 8080, and 27017, which are for web servers, except for 5432 and 27017, which are for PostgreSQL and MongoDB respectively.

My `Vagrantfile` looks like this:

```
Vagrant.configure("2") do |config|
  config.vm.box = "ubuntu/bionic64"

  # we're opening 3000, 8000, and 8080 because they're the most
  # likely to be used during your webdev process.
  #
  # if you need more ports, copy paste the examples and change the numbers
  config.vm.network "forwarded_port", guest:3000, host:3000
  config.vm.network "forwarded_port", guest:5432, host:5432
  config.vm.network "forwarded_port", guest:8000, host:8000
  config.vm.network "forwarded_port", guest:8080, host:8080
  config.vm.network "forwarded_port", guest:27017, host:27017

  # this will run initial setup
  config.vm.provision :shell, path: "bootstrap.sh"
end

```

I'm sure there's a way for me to loop over and forward an array of ports, but I'll work that out later. The important thing is that if we need to forward additional ports, we can do so inside of this file, following the same syntax we've already got.

So, the `config.vm.box` tells us what OS to use, the `config.vm.network` tells us how to deal with networking - but what does the `config.vm.provision` do? Well, to answer that question, I'll tell you about how I ended up settling on it.

## Provisioning
So, initially when I installed Vagrant and spun up a VM, I was using it pretty much the same way that I used my actual Linux box. I was installing stuff on it, even using vim within the VM to edit and write code. But, one of the things that appealed to me about VMs is that they let you set up an environment exactly the way that you want it. What's the best way to go about that?

Well, one thing we can do is package our entire VM image as a box. This is a nice idea, but it means that we'll have to upload the image somewhere, and download the whole thing every time we want to get another machine set up.

The alternative to this is that we "provision" our machine automatically. Rather than packaging everything together, we can simply have a set of scripts that will automatically install our utilities when we first set up our VM. We can use a tool like [Ansible](https://www.ansible.com/) for this (and I'll move over to it eventually), but I opted for a simple bash script, which is what we're doing when we see the line `config.vm.provision :shell, path: "bootstrap.sh"`. The `Vagrantfile` references a `bootstrap.sh` script file that is simply a list of bash commands that will install and configure stuff for us.

```
#!/usr/bin/env bash

apt-get update
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.34.0/install.sh | bash
nvm install node
sudo apt-get install -y npm
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 9DA31620334BD75D9DCB49F368818C72E52529D4
echo "deb [ arch=amd64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.0.list
sudo apt-get update
sudo apt-get install -y mongodb
sudo systemctl start mongod
sudo systemctl enable mongod
sudo apt-get install -y postgresql postgresql-contrib
sudo su - postgres -c "createuser vagrant"
sudo mkdir /data
sudo mkdir /data/db

```

As you can see, we're mostly adding PPAs and installing stuff, with the addition of a couple of operations where we're creating a user for our PostgreSQL installation and a data folder for our MongoDB. We just run `vagrant up`, and everything else takes care of itself.

All in all, this process is super nice, because I only have to package and pass around the `Vagrantfile` and `bootstrap.sh` script, which are only a few lines each, rather than a 500-800MB behemoth. Everything gets reliably set up, and then we're working with exactly the environment we want!

It's definitely a little bit of a process to set up (though nowhere near as involved as dual booting Ubuntu, or as expensive as buying a new computer), and I can foresee issues with getting confused about shared folders or which OS they're working in, but I'm really excited to save some of the Windows users from the nightmare of configuring their computers for a curriculum that was developed entirely for UNIX environments.

# Next Steps

I'd like to dig into Ansible, and provision machines using a playbook. I'd also like to figure out if Docker is a more feasible / efficient solution to this problem (but having a full OS that defaults to an interactive terminal and being able to just keep projects in the shared folder is so much better than rewriting a docker compose file every time you want to do something else, and Docker on Windows is annoying!). I'll probably add a docker installation to it, and I'd like to dig into emulating clusters using multiple VMs (I've been meaning to learn Kubernetes, and it'd be nice to learn to deal with the complexities of load balancing across multiple pieces of hardware)

In general, I'm focusing on the backend, and dipping into the [DevOps roadmap](https://roadmap.sh/devops). I've really been enjoying this level of abstraction and the process of thinking about systems at scale! Now that I've had a taste of containerization and virtualization, I'd like to virtualize/containerize and try scaling some projects. I've got a Raspberry Pi cluster in the works, and want to try setting up a CI/CD pipeline and a webserver on it, with some cute extras like home automation (check out my [server for doing BLE transmissions to a YN360 RGB light](https://github.com/tttaaannnggg/node-yn360)!).

I'm pretty excited to delve a bit deeper into this world, and am pretty excited to see what comes out of it! Any suggestions as far as what technologies I should look at next?