If you've ever heard (or uttered), the phrase "but it works on my machine", then you have experienced one of the most common and pervasive challenges of distributed development. Since you are here you probably have a desire to overcome this common challenge of distributed development, ie having a consistent build environment that works the same way on a developer's machine as it does in the build system (Azure DevOps/Jenkins/GitLab CI/etc).
If the above paragraph has you convinced, TL;DR to the magic!
There have been many ways to work towards repeatable builds, here are a few.
- CocoaPods (iOS dependency package management)
- Nuget packages (.NET dependencies)
- package-lock.json (npm/yarn for Javascript dependencies)
- requirements.txt or Pipfile (Python dependencies)
- ad nauseam repetition for every language out there (Go/Ruby/Elixir/Haskell/etc)
- Dockerfile (allows combining multiple of the above into a project specific build/development environment
- Vagrantfile (like Dockerfile, but utilizes a full VM for more isolation from host changes)
The last two are the most powerful/useful as they can be used to validate and prove the repeatability of all the previous methods.
We'll start with Vagrant as it is has less sharp edges to poke ourselves with.
The basic requirements are Vagrant (available from vagrantup.com or for easier automation of the process, through your system's package manager, ie Homebrew (https://brew.sh for macOS or https://linuxbrew.sh for Linux) or Chocolatey (https://chocolatey.org for Windows) and then a virtualization tool like Virtualbox (from https://virtualbox.org or see any of the above package management tools), Parallels (requires paid license, get from https://parallels.org for macOS), VMware Workstation/Fusion (requires paid license from https://vmware.com/desktop as well as a paid Vagrant plugin from https://vagrantup.com/vmware), or Hyper-V (available in Windows 10).
Unless you have some special requirements for extreme performance or already have a license for Parallels/VMware you can use Virtualbox quite successfully.
TL;DR brew cask install vagrant virtualbox
or choco install -y vagrant virtualbox
or for Linux apt install virtualbox
and grab the AppImage from https://vagrantup.com and put it somewhere.
There are two main sources for boxes:
- You can use an existing prebuilt box from the Vagrant site though some of the boxes may be hosted externally (ie Microsoft hosts their own images, Ubuntu hosts their own images), but many others uploaded their boxes directly to the service before Hashicorp started charging for that privilege or are exempt for paying due to being an open source project or similar.
- You can build your own image from a template using Packer, and really this is MUCH easier than it sounds.
- This the best way to get a fully up to date VM and is actually the best legal way to get a VM of certain operating systems like Windows or macOS or Red Hat Enterprise Linux.
- "Hosting" a box you've built is pretty easy, you can simply reference the direct URL to the box on a storage service (S3/Azure Blob Storage/etc), but for the nicest user experience you can generate a
metadata.json
that describes a bit more about your box like which providers (virtualization tools) it supports, and what "version" of the box it is, whether that is the date it was last built and/or patched or the major/minor operating system version contained in the VM (having both bits can be even more handy).
Once you've picked a box like the Chef Bento Ubuntu 18.04 (or built one), you can cd
to the directory for your project and then either vagrant init bento/ubuntu-18.04
or if you've built your box you will need to vagrant add --name MyBox /path/to/your/MyBox.box
and then vagrant init MyBox
.
Before you run vagrant up
you should examine the Vagrantfile generated by the vagrant init
as there are a TON of comments, not all of them are applicable, and for those who don't use the command line a lot, you will probably want to uncomment the vm.provider virtualbox do |vb|
and the matching end
line and then uncomment the vb.gui
line and change it to true
if you want to interact with the GUI inside the VM instead of via CLI using SSH. Whether there is a GUI to interact with depends a lot on the scripts that went into building the box, the Bento Ubuntu for example probably doesn't have a GUI, but if you build a Windows template you pretty much can't not have a GUI unless you are doing something special like Nanoserver or Server Core. You will also see that you can increase the amount of RAM/cores that the VM can use, but not the storage. Most VMs use a dynamically growing virtual drive that has a specific cap on the maximum size based on the template used to build it, but 60GB is pretty common. Once you've made your desired modifications you can run vagrant up
. This takes a while whether you are pulling down a public box or built your own, though the downloading tends takes a lot longer than the simple extraction into your workspace that happens after you've built your own.
So what is the advantage of this over installing the hundreds (thousands?) of packages required to build your project on your host machine?
If you were paying attention when looking in the Vagrantfile, you may have noticed a section like vm.provision
, this defines a script or command that is run once after the initial vagrant up
, but can also be called explicitly (hopefully it is idempotent in that case), with vagrant reload --provision
or if you have run vagrant halt
you can run vagrant up --provision
. This allows you to automate setting up your application's development environment and if something starts getting really weird and you are having trouble figuring out why, you can run vagrant destroy
and then vagrant up
to start over from scratch and see if one of the dependencies didn't install or if it was some change you had made after the initial setup that broke things. There is also a snapshot
command built into Vagrant that allows you to make a checkpoint that you can revert back to in case you are going to do something that you know might be destructive, like an upgrade of ALL 1,358 npm packages that your project has pulled in.
So you tried some of the public boxes but they didn't quite do what you wanted, or you needed something different and trying to change that box broke everything or required running a provision script that basically rebuilt the world? As mentioned before it is pretty easy to build a box yourself without a lot of drama.
Again there are a couple options, but the two most commonly used are:
vagrant package
which takes an existing box that you have made some customizations to (hopefully repeatably) but now you want to output a new box so your team doesn't have to wait 2 hours for all your custom stuff to run.packer build --only virtualbox-iso mybox.json
which uses the Packer tool and a template file to install the operating system and configure it to your liking, though it is also possible to reuse an existing box in this scenario as well.
Packer is an extremely useful tool also built by the folks at Hashicorp, and it makes building a consistent base image easy and repeatable, and it supports outputting for all the providers mentioned before, but it can also build and deploy an AWS AMI, an Azure VM, a Rackspace VM, or many many other types of images (including Docker ones...).
You can install it like Vagrant, by using your favorite package manager, brew install packer
or choco install -y packer
.
Once you have it installed you may want to find some existing templates to draw inspiration from. The best type of template is one that is popular and well maintained, ie has code commits in the last 2-3 months, because in software as in life, the pursuit of perfection is neverending.
The best examples I've come across have been the Chef Bento project (where Chef builds VMs of all the client operating systems that their configuration management software supports for continuous testing), and the Boxcutter project (which sadly has greatly slowed in development/improvements, but still has a few good tricks up their sleeves in their scripts). There are some other good examples on GitHub if you search packer template SomeOS
but sometimes you'll run into weird issues depending on whether they were trying to work around issues in tooling that has since been patched. For Windows mwrock
aka Matt Wrock has some good blog posts and templates, but he hasn't always updated them for the latest releases of Windows 10 or Windows Server because he moved out of the front lines of building/testing into a different role.
A basic example of building your own Windows 10 Enterprise evaluation VM using the things I've explained above (good for 90 days before you have to recreate and/or rebuild with vagrant destroy
and vagrant up
, saving your data elsewhere will be covered later).
To recap what you TL;DR'd past, you will need vagrant
, virtualbox
, and packer
installed.
macOS having installed Homebrew from https://brew.sh
brew cask install vagrant virtualbox ; brew install packer
Windows having installed Chocolatey from https://chocolatey.org and closed and reopened an "elevated" Powershell prompt (right click and 'Run as Administrator')
choco install -y vagrant virtualbox packer
cd $HOME
mkdir projects
cd projects
git clone https://github.com/chef/bento chef-bento-packer-templates
cd chef-bento-packer-templates
cd packer_templates
cd windows
packer build --only virtualbox-iso windows-10.json
# This takes a LONG time, but a recently merged fix should at least allow it to complete
# After the build is complete there should be a windows-10-virtualbox.box in this directory
vagrant box add --name Win10EntEval windows-10-virtualbox.box
cd $HOME/projects
vagrant init Win10EntEval
vagrant up