Since the begin of this week I have been starting to use Vagrant as a tool to orchestrate the creation of virtual machines (for VirtualBox). Background is that at work we are maintaining a software collection with tools for data analysis and development of in-house software projects. In order to have additional flexibility and portabililty the idea is to provide a series of Docker images and virtual machines with all packages pre-installed, such that the barrier for distribution and usage can be placed as low as possible. However as the plural already indicates we are not talking a single instance, but indeed a multitude of images/appliances - hence there more we can automate the better.

While Docker is easily scriptable from the outside world (e.g. via CMake), creating VirtualBox appliances up until has been a rather time-consuming process. While there always is the possibility to work with snapshots and exported appliances the fact of going through the VirtualBox GUI itself results in hardly scalable manual labour. Hence enter Vagrant:

Vagrant is an open-source software product for building and maintaining portable virtual development environments. The core idea behind its creation lies in the fact that the environment maintenance becomes increasingly difficult in a large project with multiple technical stacks. Vagrant manages all the necessary configurations for the developers in order to avoid the unnecessary maintenance and setup time, and increases development productivity. Vagrant is written in the Ruby language but its ecosystem supports development in almost all major languages.

The final point of this short description makes for a quick start to get off the ground: given that all the websites (be it local or online) are built using Jekyll, which in turn is written in Ruby, no additional learning curve is required. Writing a Vagrantfile is no different from writing a Rakefile - even though the main set of instructions are part of a DSL, still the full power of the Ruby language is available if so required.

  Vagrant.configure("2") do |config|

    config.vm.box      = "debian/jessie64"
    config.vm.hostname = "debian-8-64bit-lxde"

    # Share an additional folder to the guest VM. The first argument is
    # the path on the host to the actual folder. The second argument is
    # the path on the guest to mount the folder. And the optional third
    # argument is a set of non-required options.
    config.vm.synced_folder Dir.pwd.gsub("/virtualization/debian/8",""), "/tmp/vagrant"

    # --- Provider-specific configuration (VirtualBox)

    config.vm.provider "virtualbox" do |vb|
      # Name of the machine shown in the VirtualBox GUI
      vb.customize ["modifyvm", :id, "--name", "Debian 8 (amd64, LXDE)"]
      # Display the VirtualBox GUI when booting the machine
      vb.gui = true
      # Customize the amount of memory on the VM:
      vb.customize ["modifyvm", :id, "--memory", "1280"]
      # Video RAM [MB]
      vb.customize ["modifyvm", :id, "--vram", "32"]
      # Enabling DNS proxy in NAT mode
      vb.customize ["modifyvm", :id, "--natdnsproxy1", "on"]
      # Using the host's resolver as a DNS proxy in NAT mode
      vb.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]
      # Enable clipboard (bidirectional)
      vb.customize ["modifyvm", :id, "--clipboard", "bidirectional"]
    end

    # --- Provisioning with a shell script

    config.vm.provision "shell", path: "../../vagrant.sh"

  end

As is the case with Docker starting up an instance of the virtualized environment happens by a rather simple command:

  vagrant up

Depending on what the status of your local setup is at that point in time either the machine will be fired up directly or (if necessary) a virtual machine will be created from a base image (or “box” in Vagrant lingo). In the example below indeed I am starting pretty much from scratch (minus the creation of a base box):

  Bringing machine 'default' up with 'virtualbox' provider...
  ==> default: Importing base box 'debian/jessie64'...
  ==> default: Matching MAC address for NAT networking...
  ==> default: Checking if box 'debian/jessie64' is up to date...
  ==> default: Setting the name of the VM: 8_default_1478188236968_1998
  ==> default: Clearing any previously set network interfaces...
  ==> default: Preparing network interfaces based on configuration...
      default: Adapter 1: nat
  ==> default: Forwarding ports...
      default: 22 (guest) => 2222 (host) (adapter 1)
  ==> default: Running 'pre-boot' VM customizations...
  ==> default: Booting VM...
  ==> default: Waiting for machine to boot. This may take a few minutes...
      default: SSH address: 127.0.0.1:2222
      default: SSH username: vagrant
      default: SSH auth method: private key
      default:
      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...
  ==> default: Machine booted and ready!

Once all the scripts involved are tested, repetition is rather straight forward and painless… or at least could be weren’t there constant network outages interrupting the retrieval of software packages from online resources. Right now the plan is to use tomorrow morning for a first larger test, trying to run through all the build recipes and then check what I am left with in the end.