Virtual server using HashiCorp Vagrant

Setting up a virtual server using vagrant

Vagrant from Hashicorp is a program that provides a declarative interface to create and manage Virtual Machines (VM). It defines the state of a VM and instructs VM providers (ex: VirtualBox, VMWare) how to create/update/manage it.

Why is this needed?

Creating a simple VM is relatively easy thanks to the VM providers. But managing VMs and sharing their state with other users is very complex and this is where Vagrant does it’s magic.

  • Provisioning: Instead of manually specifying the configuration of a machine, the state is declared in a Vagrantfile. These files allow the user to define configuration such as IP addresses, shared folders and even accept initialization scripts to install necessary packages when the VM is created.
  • Replication: Once a Vagrantfile is defined, this file can be shared with other users/developers who can then simply create a replica of the machine by simply running a command.

How Does It Work?

Note: Vagrant doesn’t actually create virtual machines. It instructs the VM provider to create/update/delete a VM based on the configuration.


To follow along this tutorial, the following are required

  • Basic knowledge of shell/command line.
  • An machine with a compatible OS and shell access with enough resources to run VMs.
  • A VM provider. My personal recommendation is VirtualBox.
  • A Vagrant installation.

Necessary Terminology

Term Description
Virtual Machine (VM) / Guest A software emulation of a real machine.
Host (machine/OS) The environment which hosts the Virtual Machine (ex: a laptop or even a hypervisor).
VM Provider A program such as VMWare, VirtualBox etc., that creates/manages VMs.
Vagrant Box A base image of an Operating system (with additional packages) that’s pre-built for convenience. These are listed in the Vagrant Box Catalog.

Basic Setup

The following is a barebones Vagrantfile

Vagrant.configure("2") do |config| = "ubuntu/bionic64"
    config.vm.provider "virtualbox" do |v|
        v.memory = 2048
        v.cpus = 1
  • "2" stands for the version of configuration object. At the time of writing this post, values 1 and 2 are supported.
  • is the Vagrant Box corresponding to the OS (+additional packages) that will run on this VM. This box will be downloaded on the first run of this VM. The full list of supported VMs is on the Vagrant Box Catalog.
  • config.vm.provider configures the VM provider (virtualbox in this case).
    • v.memory: the amount of RAM in bytes to allocate to this VM.
    • v.cpus: the number of virtual CPUs to allocate to this VM.
    • an optional name for this VM.

Place this file in a folder of choice and run vagrant

$ mkdir Documents/vagrant
$ cd Documents/vagrant
$ vagrant up

This will trigger vagrant to download the box (if not already locally available), configure the VM provider and start up the VM.

Note: All the following bash commands are executed in the same directory as the vagrant file unless otherwise specified.

Vagrant also creates an SSH key pair for this machine and sets up an ssh-agent on the VM. To check the ssh config

$ vagrant ssh-config

# This is a sample output
Host default
  User vagrant
  Port 2222
  UserKnownHostsFile /dev/null
  StrictHostKeyChecking no
  PasswordAuthentication no
  IdentityFile <path>/.vagrant/machines/default/virtualbox/private_key
  IdentitiesOnly yes    
  LogLevel FATAL

To ssh into the machine

$ vagrant ssh


One of the key features of Vagrant is declarative configuration of the VM state. This usually involves installing/updating necessary packages, configuring users and/or running custom scripts, a process dubbed provisioning.

There are multiple ways of defining the provisioning script. The following example shows the inline method.

Anything defined within the <<SCRIPT...SCRIPT block is interpreted as a shell script by Vagrant.

$basicscript= <<SCRIPT
sudo apt update
sudo apt -y dist-upgrade
sudo apt -y autoremove

Vagrant.configure("2") do |config| = "ubuntu/bionic64"
    config.vm.provider "virtualbox" do |v|
	    v.memory = 2048
	    v.cpus = 1
	config.vm.provision "shell" do |s|
    	s.inline = $basicscript

config.vm.provision defines a shell provisioner block and inline indicates that it’s an inline script.

With this Vagrantfile, Vagrant will create the VM with the specified box and once the basic setup is complete, the inline provisioning script will be executed (updating packages in this case).

Provisioning scripts are only run on the first instantiation of $ vagrant up, i.e, while creating the VM for the first time. For an existing VM, use $ vagrant provision to execute the provisioning script.

Alternatively, a separate script file can be used for more complex actions (in order to keep the Vagrantfile simple). This is done using the path option.

Vagrant.configure("2") do |config|
	config.vm.provision "shell" do |s|
      s.path = ""

Vagrant copies the script file to the VM and executes it while provisioning. A remote path where the script can be fetched can also be used.

s.path = ""

Sharing Folders

Vagrant makes it easier than ever to shares files/folders between the host and the guest.

The synced_folder instruction is used for this.

config.vm.synced_folder  "path on the host" , "path on the guest"

In the following example, the contents of the host’s Documents folder can be accessed in the VM at the /Docs directory.

Vagrant.configure("2") do |config|
    config.vm.synced_folder  "~/Documents" , "/Docs"

It’s important to note that by default, the folder in which the Vagrantfile exists is shared with the VM at the /Vagrant directory. This can be disabled if necessary.

config.vm.synced_folder ".", "/vagrant", disabled: true

Network Configuration

In order to be able to access services on the VM externally (ex: a web server) Vagrant supports basic network configuration.

An IP address can be assigned to the VM. "public_network", ip: ""

If a web server is running on the VM at port 80 then it can be accessed at

A hostname can also be used.

config.vm.hostname = "myvm.local" "public_network", ip: "", hostname: true

This adds the host entry to the /etc/hosts file of the host machine. myvm myvm.local

If a web server is now running on the VM at port 80 then it can be accessed at http://myvm.local or simply http://myvm

Vagrant also supports port forwarding "forwarded_port", guest: 80, host: 8080

Multiple VMs

And finally, all the configuration options mentioned above can be replicated to create N machines using the same Vagrantfile. The define keyword is used to group options for a single VM.

$basicscript= <<SCRIPT
sudo apt update
sudo apt -y dist-upgrade
sudo apt -y autoremove

$vm1script= <<SCRIPT
# Add custom commands to be run only for VM 1

$vm2script= <<SCRIPT
# Add custom commands to be run only for VM 2

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

    # Common settings
    config.vm.synced_folder "./", "/srv"
    config.vm.provision :shell, inline: $basicscript
    # Settings for VM 1
    config.vm.define "vm1" do |master| "private_network", ip: ""
        vm1.vm.provision "shell" do |s|
            s.inline:  $vm1script, #run the vm1script defined above

    # Settings for VM 2
    config.vm.define "vm2" do |minion| "private_network", ip: ""
        vm2.vm.provision "shell" do |s|
            s.inline:  $vm2script, #run the vm2script defined above


  1. Official Vagrantfile reference.
Last updated on Jan 30, 2022 13:11 +0100