manski's blog

Vagrant Tutorial – From Nothing To Multi-Machine

As developers, we sometimes want to quickly test some software. Instead of installing it directly on our developer machine, it’s better to install it in a virtual machine (VM). But if you don’t have a VM ready, setting one up usally takes a lot of time – and there goes your productivity.

Fortunately, there is a solution: Vagrant

Vagrant is a free tool that lets you quickly spin-up fresh VMs out of thin air. It can even spin-up multiple VMs at the same time.

This article is step by step tutorial for getting from nothing to a multi-VM setup where the VMs can talk to each other.

The Goal

At the end of this tutorial, we’ll have 3 virtual machines, one called “master” and two nodes, that can find each other via their hostnames. Except for Vagrant, no external software is required to achieve this.

The setup we’ll be creating is just a foundation you can expand on. It won’t do anything meaningful. It’s just to get you the infrastructure to do something meaningful.

If you’re just interested in the end result, skip ahead to Putting everything together.

Notes Before We Start

To be able to follow this tutorial, it’s helpful if you have a basic understanding of the following:

  • How to interact with the command line of your operating system – since Vagrant is controlled from the command line.
  • What SSH is for.
  • What virtual machines are in general.

For Vagrant I recommend that you have a fast internet connection. The VMs created by Vagrant are downloaded from the internet and are usually 600 MB or bigger. A slow internet connection will work but you’ll have to wait for a long time for the downloads to finish.

As for software versions, this tutorial was tested against Vagrant 1.8.5 and VirtualBox 5.1.


To be able to use Vagrant you need two things: Vagrant itself and a hyper-visor.

On Windows you may also want to install a command line replacement.

You can download Vagrant here:

As hyper-visor, you can choose between VirtualBox, VMWare (Fusion), and Hyper-V.

The easiest option is VirtualBox. It’s free and available on every platform.

If you have Hyper-V installed, don’t install VirtualBox. Hyper-V is incompatible with other hyper-visors. (See here for more information.)

If you have VMWare Fusion installed on your Mac, please note that the VMWare provider for Vagrant costs money – even if you already have a VMWare Fusion license. If you don’t want to spend money, just use VirtualBox. It can be installed alongside VMWare Fusion.

To verify that Vagrant is installed, type on the command line:

> vagrant -v
Vagrant 1.8.5

Starting and Interacting with Your First VM

To start your first VM, first create an empty directory somewhere and cd into it.

Then execute the following two commands:

> vagrant init hashicorp/precise64
> vagrant up

This will start a virtual machine running Ubuntu 12.04 (Precise Pangolin).

Note: If you’re using Hyper-V instead of VirtualBox, you have to call vagrant up --provider=hyperv instead of just vagrant up. Alternatively, you may want to configure Hyper-V as the default provider for Vagrant. See this article on how to do this.

To ssh into the VM, call:

> vagrant ssh

To get out of the VM, hit Ctrl + D.

To stop and delete the whole VM, call:

> vagrant destroy -f

This will delete the VM and all of its resources (i.e. the virtual hard drive) from your computer.

The Vagrantfile

When you called vagrant init hashicorp/precise64 earlier, Vagrant created a file called Vagrantfile in the current directory.

This file is everything Vagrant needs to do its work.

The file created by vagrant init contains lots of documentation and is a good starting point for customizing the VM.

However, for the purpose of this tutorial, let’s reduce the file to its bare minimum:

Vagrant.configure("2") do |config| = "hashicorp/precise64"

In the second line you can see the value you passed to vagrant init earlier: hashicorp/precise64

This is the name of the base image (think: virtual hard disk) used for the VM to create. Such an image is called a box in Vagrant terminology.

When Vagrant creates a VM from a box (base image), it actually creates a copy of this image. Thus, any changes done inside of the VM are lost when the VM is destroyed (via vagrant destroy). A VM can’t modify its base image.

Choosing the Right Box (Base Image)

Vagrant boxes are downloaded by default from HashiCorp’s Atlas system. All available boxes can be searched here:

For a single, specific operating system there are usually many boxes to choose from. For example, searching for a Ubuntu 14.04 box returns (among others):

  • ubuntu/trusty64
  • puphpet/ubuntu1404-x64
  • boxcutter/ubuntu1404
  • bento/ubuntu-14.04

Unfortunately, boxes on Atlas differ in quality since anyone can upload a box. A low quality box prevents you from using Vagrant features, such as setting the hostname of VM or creating a private network.

During my testing I found that the ubuntu/... boxes have very low quality (which is surprising given that they’re are created by Canonical, the company behind Ubuntu).

Also, HashiCorp (the company behind Vagrant) only provides boxes for Ubuntu 12.04. So they can’t be selected as source of high quality boxes either. (During my testing, even the hashicorp/precise64 box had its problems.)

The Vagrant documentation about official boxes recommends to use the boxes from the bento namespace (apparently created by the team at Chef).

During my (limited) tests they worked flawlessly and so they’re my recommendation, too. We’ll use them for rest of the tutorial. You can find them here:

Note: Unfortunately, the bento boxes don’t work with Hyper-V. So if you’re using Hyper-V with Vagrant, you have to find a different box.

Updating a VM after Vagrantfile Has Changed

Modifying a Vagrantfile while its VM is running has no effect on the running VM.

To “synchronize” the VM with its Vagrantfile, you can either:

  1. call vagrant reload or
  2. call vagrant destroy -f followed by vagrant up

Note: If you’re using provisioning (see below) and changed the provisioning data, you need to call vagrant reload --provision in the first case.

To make things simpler, I recommend you’re using the second option for this tutorial.

Multi-Machine: The Naive Way

So far we’ve always started a single VM.

We can also start multiple VMs with a single Vagrantfile. This is called Multi-Machine.

The easiest (or most naive) way to create a multi-machine is with a Vagrantfile like this:

Vagrant.configure("2") do |config|
  config.vm.define "master" do |subconfig| = "bento/ubuntu-16.04"

  config.vm.define "node1" do |subconfig| = "bento/ubuntu-16.04"

  config.vm.define "node2" do |subconfig| = "bento/ubuntu-16.04"

This will create 3 VMs (master, node1, node2).

To ssh into any of the VMs, just specify its name. For example, to ssh into node1, call:

> vagrant ssh node1

To destroy all VMs, just call:

> vagrant destroy -f

This is exactly the same command as for a single VM.

Multi-Machine: The Clever Way

The previous multi-machine Vagrantfile had lots of copied code.

The same setup can also be described in a more “programmer-like” manner:

BOX_IMAGE = "bento/ubuntu-16.04"

Vagrant.configure("2") do |config|
  config.vm.define "master" do |subconfig| = BOX_IMAGE
  (1..NODE_COUNT).each do |i|
    config.vm.define "node#{i}" do |subconfig| = BOX_IMAGE

Here we:

  • Moved the box name into a constant (BOX_IMAGE).
  • Converted the “nodeX” definitions into a for each loop where NODE_COUNT describes the number of nodes to create.

Connecting the VMs via Network

The setup in the previous section created 3 VMs. However, up until now these VMs had no way of communicating with each other.

Important: Before you do this section, please call vagrant destory -f. This makes things easier.

To fix this we need three things.

First, each VM needs a unique hostname. By default, each of the VMs has the same hostname (vagrant). To change this, we need to add a configuration like the following to each VM definition in the Vagrantfile:

subconfig.vm.hostname = ""

Next, we need a way of getting the IP address for a hostname. For this, we’ll use DNS – or mDNS to be more precise.

On Ubuntu, mDNS is provided by Avahi. To install Avahi on each node, we’ll use Vagrant’s provisioning feature.

Before the last end in the Vagrantfile, we’ll add this code block:

config.vm.provision "shell", inline: <<-SHELL
  apt-get install -y avahi-daemon libnss-mdns

This will call apt-get install -y avahi-daemon libnss-mdns on every VM.

Note: By default, provisioning is only done the first vagrant up. See here for more details.

Last, we need to connect the VMs through a private network.

For each VM, we need to add a config like this (where each VM will have a different ip address): :private_network, ip: ""

Putting everything together

Putting everything mentioned in the previous section together results in a Vagrantfile like this:

# -*- mode: ruby -*-
# vi: set ft=ruby :

# Every Vagrant development environment requires a box. You can search for
# boxes at
BOX_IMAGE = "bento/ubuntu-16.04"

Vagrant.configure("2") do |config|
  config.vm.define "master" do |subconfig| = BOX_IMAGE
    subconfig.vm.hostname = "master" :private_network, ip: ""
  (1..NODE_COUNT).each do |i|
    config.vm.define "node#{i}" do |subconfig| = BOX_IMAGE
      subconfig.vm.hostname = "node#{i}" :private_network, ip: "10.0.0.#{i + 10}"

  # Install avahi on all machines  
  config.vm.provision "shell", inline: <<-SHELL
    apt-get install -y avahi-daemon libnss-mdns

You can now call vagrant up and then ssh into any of the VMs:

> vagrant ssh node1

From there you can ping any other VM by using their hostname (plus .local at the end):

> ping node2.local

Wrap Up

As you’ve seen, it just takes a Vagrantfile with 22 lines and a call to vagrant up to create multiple VMs in one step. Easy, isn’t it?

To continue from here, have a look at these resources:

You can also leave a comment below.


  • 2016-10-02

    • Added note about bento box not being available for Hyper-V
    • Found mention of the Bento boxes in the official Vagrant documentation; changed links to there
  • 2016-10-01

    • Added link to command line replacements on Windows
    • Added NODE_COUNT to Vagrantfile
  • 2016-09-29

    • Initial release


  1. KirillVolkovich said: ∞

    Thank you for sharing useful information. But it is impossible to copy-paste code with correct formatting. All code became one-lined.

  2. KirillVolkovich said: ∞

    Addition to last comment.

    Problem reproduced if select last big code example with hash marked top comments.

    • Sebastian Krysmanski (post author) replied: ∞

      Sorry to hear that copy doesn’t work for you. I can’t reproduce the problem, though. For me copying works fine in Firefox, Chrome, and Internet Explorer. What browser and OS are you using?

  3. Don Morton said: ∞

    Hi, thank you very much for this. I’m new enough to this, so I may be missing some details. I need to use the ubuntu/xenial64 box, and I know that’s been problematic for some in setting up their private networks.

    When I tried your approach, it didn’t work, but when I looked at documentation for private_network I saw a syntax of “private_network” rather than the private_network: that you show in your tutorial, and now it works.

    • Don Morton replied: ∞

      Sorry, error in the above – I meant to say that when I used your :private_network syntax, it didn’t work for me, but using “private_network” did.

  4. Mukesh Purohit said: ∞

    thanks a lot for sharing these examples. These were really helpful and worked for me perfectly.

  5. joseph said: ∞

    Thank you very much. Nice post.

  6. Stephen said: ∞

    Very helpful material, thanks a lot for it.

    Question, what is the essence of the |config| / |subconfig| ?

    • Sebastian Krysmanski (post author) replied: ∞

      It’s some Ruby construct. It basically defines a variable for the inner scope – at least as far as I can tell. I’m by no means an expert on Vagrant or Ruby.

Leave a Reply

Your email address will not be published. Required fields are marked *