In a previous post I showed how to create a vagrant box from scratch and add it to Vagrant: Creating a custom Vagrant Box

Traditionally you will place the Vagrant Box in a web server, and then use it in a Vagrantfile as follows:

VAGRANTFILE_API_VERSION = "2"
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.box = "vagrant_rhel6_vanilla"
  config.vm.box_url = "http://vagrant.example.com/vagrant/vagrant_rhel6_vanilla.box"
...

In this post I will show you how to add versioning to your vagrant boxes, so that whenever the base box is changed/updated/patched your end-users/developers will be automatically notified and will be able to get the latest vagrant box with a simple ‘vagrant box update’ command.

1) Create the updated Vagrant Box
(e.g. vagrant_rhel6_vanilla_010.box for version 0.1.0) – you can use the procedure: Creating a custom Vagrant Box

2) Get a checksum of the box – You will need it later for the JSON Box catalog

$ shasum vagrant_rhel6_vanilla_010.box
b42efdb1e789976a45ffc26a321f00a42932f792  vagrant_rhel6_vanilla_010.box

OR

$ openssl sha1  vagrant_rhel6_vanilla_010.box
SHA1(vagrant_rhel6_vanilla_010.box)= b42efdb1e789976a45ffc26a321f00a42932f792

3) Upload the Box to the Vagrant Box web server repository:

I recommend to use the following web directory structure:
../vagrant/boxes serves the actual boxes/VMs
../vagrant serves the Box metadata JSON file (I explain what this is below)

vagrant/
|-- boxes
|   |-- vagrant_rhel6_vanilla_010.box
`-- vagrant_rhel6_vanilla.json

Also, ensure the web server returns json as the Content-Type.
For Apache you need to check/add that the following configuration is present:
AddType application/json .json

To verify if your webserver is returning the correct Content-Type:

$ curl -sI  http://vagrant.example.com/vagrant/vagrant_rhel6_vanilla.json | grep Content-Type
Content-Type: application/json

4) Create a Box catalog to be served by the web server:

The box catalog is a JSON metadata file allows you to server multiple versions of a box and enable update status notification.
Fields:
name: Name of the Vagrant Box, this is what you will put under config.vm.box on the Vagrantfile
Versions: This is an array/list/collection where you will put the different versions you are hosting/serving
url: This is the actual location of the box, this is what you will put under config.vm.box_url on the Vagrantfile

$ cat vagrant_rhel6_vanilla.json

{
  "name": "vagrant_rhel6_vanilla",
  "description": "This box contains RHEL 64-bit.",
  "versions": [
    {
      "version": "0.1.0",
      "providers": [
        {
          "name": "vmware_desktop",
          "url": "http://vagrant.example.com/vagrant/boxes/vagrant_rhel6_vanilla_010.box",
          "checksum_type": "sha1",
          "checksum": "b42efdb1e789976a45ffc26a321f00a42932f792"
        }
      ]
    }
  ]
}

5) Modify the Vagrantfile to use the json box catalog file, instead of the actual box name

VAGRANTFILE_API_VERSION = "2"
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.box = "vagrant_rhel6_vanilla"
  config.vm.box_url = "http://vagrant.example.com/vagrant/vagrant_rhel6_vanilla.json"
...

The first time you move from traditional box to versioned box, you need to delete the traditional box that is cached on your system.
This is only needed if you have a traditional box cached.

$ vagrant box list
vagrant_rhel6_vanilla      (vmware_desktop, 0)  <-- Traditional Box

$ vagrant box remove vagrant_rhel6_vanilla
Removing box 'vagrant_rhel6_vanilla' (v0) with provider 'vmware_desktop'...

6) Vagrant up and see Vagrant download the versioned Box

$ vagrant up
Bringing machine 'default' up with 'vmware_fusion' provider...
==> default: Box 'vagrant_rhel6_vanilla' could not be found. Attempting to find and install...
    default: Box Provider: vmware_desktop, vmware_fusion, vmware_workstation
    default: Box Version: >= 0
==> default: Loading metadata for box 'http://vagrant.example.com/vagrant/vagrant_rhel6_vanilla.json'
    default: URL: http://vagrant.example.com/vagrant/vagrant_rhel6_vanilla.json
==> default: Adding box 'vagrant_rhel6_vanilla' (v0.1.0) for provider: vmware_desktop
    default: Downloading: http://vagrant.example.com/vagrant/boxes/vagrant_rhel6_vanilla_010.box
    default: Calculating and comparing box checksum...
==> default: Successfully added box 'vagrant_rhel6_vanilla' (v0.1.0) for 'vmware_desktop'!
==> default: Cloning VMware VM: 'vagrant_rhel6_vanilla'. This can take some time...
==> default: Checking if box 'vagrant_rhel6_vanilla' is up to date...
==> default: Verifying vmnet devices are healthy...
...

7) Verify cached Vagrant Box is the correct version

$ vagrant box list
vagrant_rhel6_vanilla      (vmware_desktop, 0.1.0)

At this point you have a Vagrant environment that supports versions and update status notifications, let’s create a new version of the Box and see it in action.

8) The Vagrant administrator creates an updated box
(e.g. vagrant_rhel6_vanilla_010.box for version 0.1.1) – you can use the procedure: Creating a custom Vagrant Box

9) The Vagrant administrator places the Vagrant Box in the web server

vagrant/
|-- boxes
|   |-- vagrant_rhel6_vanilla_010.box
|   `-- vagrant_rhel6_vanilla_011.box  <-- New Vagrant box
`-- vagrant_rhel6_vanilla.json

10) The Vagrant administrator adds the new Vagrant Box to the Box manifest Catalog

$ cat vagrant_rhel6_vanilla.json

{
  "name": "vagrant_rhel6_vanilla",
  "description": "This box contains RHEL 64-bit.",
  "versions": [
    {
      "version": "0.1.0",
      "providers": [
        {
          "name": "vmware_desktop",
          "url": "http://vagrant.example.com/vagrant/boxes/vagrant_rhel6_vanilla_010.box",
          "checksum_type": "sha1",
          "checksum": "b42efdb1e789976a45ffc26a321f00a42932f792"
        }
      ]
    },
    {
      "version": "0.1.1",
      "providers": [
        {
          "name": "vmware_desktop",
          "url": "http://vagrant.example.com/vagrant/boxes/vagrant_rhel6_vanilla_011.box",
          "checksum_type": "sha1",
          "checksum": "8ca8207afced5ca4ba387547fe9ef2b79a7d2b5b"
        }
      ]
    }
  ]
}

That’s it! from a Vagrant administrator perspective

11) End user/developer experience: User gets notified on the next Vagrant up and can easily update:

$ vagrant up
Bringing machine 'default' up with 'vmware_fusion' provider...
==> default: Checking if box 'vagrant_rhel6_vanilla' is up to date...
==> default: A newer version of the box 'vagrant_rhel6_vanilla' is available! You currently
==> default: have version '0.1.0'. The latest is version '0.1.1'. Run
==> default: `vagrant box update` to update.
==> default: Verifying vmnet devices are healthy...
...

Everytime there is an update the end user gets notified and he/she can upgrade by simply:
vagrant box update

$ vagrant box update
==> default: Checking for updates to 'vagrant_rhel6_vanilla'
    default: Latest installed version: 0.1.0
    default: Version constraints:
    default: Provider: vmware_desktop
==> default: Updating 'vagrant_rhel6_vanilla' with provider 'vmware_desktop' from version
==> default: '0.1.0' to '0.1.1'...
==> default: Loading metadata for box 'http://vagrant.example.com/vagrant/vagrant_rhel6_vanilla.json'
==> default: Adding box 'vagrant_rhel6_vanilla' (v0.1.1) for provider: vmware_desktop
    default: Downloading: http://vagrant.example.com/vagrant/boxes/vagrant_rhel6_vanilla_011.box
    default: Calculating and comparing box checksum...
==> default: Successfully added box 'vagrant_rhel6_vanilla' (v0.1.1) for 'vmware_desktop'!

To verify, check cached Vagrant Boxes:

$ vagrant box list
vagrant_rhel6_vanilla      (vmware_desktop, 0.1.0)
vagrant_rhel6_vanilla      (vmware_desktop, 0.1.1) <-- New Vagrant Box will be used

To delete older cached Vagrant Box no longer needed:

$ vagrant box remove vagrant_rhel6_vanilla --box-version '0.1.0'
Removing box 'vagrant_rhel6_vanilla' (v0.1.0) with provider 'vmware_desktop'...

This improves the user/developer experience because they just need to do ‘vagrant box update‘ anytime there is an updated box from the Vagrant administrator.