Post

Vagrant: Installation, Configuration, and Usage

Vagrant is an open-source tool by HashiCorp that lets you build and manage reproducible virtual development environments with a single configuration file. Instead of manually installing an OS, configuring networking, and tweaking settings every time you spin up a new machine, you describe everything in a Vagrantfile and Vagrant handles the rest — every time, on every machine, for every team member.

Why Use Vagrant?

Benefit Description
Consistency Everyone on the team works in an identical environment
Safety Experiment freely without touching your host OS
Portability Share the Vagrantfile and teammates reproduce your environment in minutes
Automation Provisioning scripts install software automatically on first boot
Speed Destroy and recreate complex stacks with a single command

Think of a Vagrantfile as a recipe: write it once, and Vagrant follows it to build the exact same machine on demand — whether that’s a single Ubuntu server or a five-node Kubernetes cluster.

Before You Start

Hardware Requirements

  • RAM: 8 GB recommended (4 GB minimum)
  • Storage: 20 GB free disk space per VM (plan accordingly for multi-machine setups)
  • CPU: Any modern processor with hardware virtualization support (Intel VT-x or AMD-V) enabled in BIOS/UEFI

Required Software: VirtualBox

Vagrant needs a provider — a virtualization backend — to create VMs. VirtualBox is free, cross-platform, and the most widely used provider.

Download from virtualbox.org and install it before proceeding.

Note: Other supported providers include VMware, Hyper-V, and libvirt (KVM). This guide focuses on VirtualBox.


Installing Vagrant

Windows

  1. Visit developer.hashicorp.com/vagrant/downloads and download the Windows installer (.msi).
  2. Double-click the installer and follow the wizard, accepting defaults.
  3. Restart your computer after installation.
  4. Open Command Prompt or PowerShell and verify:
1
vagrant --version

macOS

Option 1 — Homebrew (recommended):

1
brew install vagrant

Option 2 — Manual: Download the .dmg from the Vagrant website, open it, and run the installer package.

Verify:

1
vagrant --version

Linux

Ubuntu / Debian:

1
2
3
4
5
6
7
8
wget -O- https://apt.releases.hashicorp.com/gpg | \
  sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg

echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] \
  https://apt.releases.hashicorp.com $(lsb_release -cs) main" | \
  sudo tee /etc/apt/sources.list.d/hashicorp.list

sudo apt update && sudo apt install vagrant

Fedora / RHEL / CentOS:

1
2
3
sudo dnf install -y dnf-plugins-core
sudo dnf config-manager --add-repo https://rpm.releases.hashicorp.com/fedora/hashicorp.repo
sudo dnf install vagrant

Verify on any Linux distribution:

1
vagrant --version

Understanding the Vagrantfile

The Vagrantfile is written in Ruby and lives in the root of your project directory. You don’t need to know Ruby — the DSL is simple and readable.

Minimal Vagrantfile

1
2
3
Vagrant.configure("2") do |config|
  config.vm.box = "ubuntu/focal64"
end

That is a fully functional Vagrantfile. Vagrant downloads the ubuntu/focal64 box from Vagrant Cloud and boots it.

Full Single-Machine Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Vagrant.configure("2") do |config|

  # Base OS image
  config.vm.box     = "ubuntu/focal64"
  config.vm.hostname = "web-server"

  # Private network with a static IP (accessible from host only)
  config.vm.network "private_network", ip: "192.168.56.10"

  # Forward host port 8080 to guest port 80
  config.vm.network "forwarded_port", guest: 80, host: 8080

  # Sync a local folder into the VM
  config.vm.synced_folder "./app", "/var/www/html"

  # VirtualBox provider settings
  config.vm.provider "virtualbox" do |vb|
    vb.name   = "web-server-ubuntu"
    vb.memory = "2048"   # MB
    vb.cpus   = 2
    vb.gui    = false
  end

  # Shell provisioner — runs once on first `vagrant up`
  config.vm.provision "shell", inline: <<-SHELL
    apt-get update -y
    apt-get install -y nginx
    systemctl enable nginx
    echo "Provisioned by Vagrant" > /var/www/html/index.html
  SHELL

end

Key Vagrantfile Directives

Directive Purpose
config.vm.box Base box (OS image) to use
config.vm.hostname Sets the VM’s hostname
config.vm.network Configures networking (private, public, forwarded ports)
config.vm.synced_folder Maps a host directory into the VM
config.vm.provider Provider-specific settings (RAM, CPUs, etc.)
config.vm.provision Automated setup scripts or tools (shell, Ansible, Chef…)

Essential Vagrant Commands

1
2
3
4
5
6
7
8
9
10
11
12
13
vagrant init ubuntu/focal64   # Create a Vagrantfile for the given box
vagrant up                    # Start (and provision on first run) the VM
vagrant ssh                   # Open an SSH session inside the VM
vagrant halt                  # Gracefully shut down the VM
vagrant suspend               # Pause (save state) the VM
vagrant resume                # Resume a suspended VM
vagrant reload                # Restart the VM and re-apply Vagrantfile changes
vagrant reload --provision    # Restart and re-run provisioning scripts
vagrant destroy               # Delete the VM and all its data
vagrant status                # Show the current state of the VM
vagrant box list              # List all locally cached boxes
vagrant box update            # Update boxes to latest version
vagrant box remove BOX_NAME   # Remove a cached box to free disk space

Warning: vagrant destroy permanently deletes all data inside the VM. Back up anything important first.


Step-by-Step: Your First Vagrant VM

1. Create a Project Directory

1
2
3
# macOS / Linux
mkdir -p ~/vagrant-projects/my-first-vm
cd ~/vagrant-projects/my-first-vm
1
2
3
# Windows
mkdir C:\vagrant-projects\my-first-vm
cd C:\vagrant-projects\my-first-vm

2. Initialize the Project

1
vagrant init ubuntu/focal64

This creates a Vagrantfile pre-configured for Ubuntu 20.04 (Focal Fossa).

3. Customize the Vagrantfile

Open Vagrantfile in any text editor and replace its contents with:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Vagrant.configure("2") do |config|
  config.vm.box      = "ubuntu/focal64"
  config.vm.hostname = "my-ubuntu"
  config.vm.network "private_network", ip: "192.168.56.10"

  config.vm.provider "virtualbox" do |vb|
    vb.name   = "My-First-Ubuntu-VM"
    vb.memory = "2048"
    vb.cpus   = 2
  end

  config.vm.provision "shell", inline: <<-SHELL
    apt-get update -y
    apt-get upgrade -y
    echo "Welcome to your Vagrant VM!" > /etc/motd
  SHELL
end

4. Start the VM

1
vagrant up

The first run downloads the base box image, which may take a few minutes depending on your connection. Subsequent starts are fast.

5. Connect via SSH

1
vagrant ssh

You now have a shell inside the VM. Try:

1
2
3
whoami
uname -a
ip addr show

6. Exit and Stop

1
2
3
exit          # Leave the SSH session
vagrant halt  # Shut down the VM
vagrant up    # Start it again later

Multi-Machine Environments

One of Vagrant’s most powerful features is the ability to define multiple VMs in a single Vagrantfile. This is ideal for simulating realistic infrastructures: web servers, databases, load balancers, and Kubernetes nodes — all on your laptop.

Basic Multi-Machine Setup

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
Vagrant.configure("2") do |config|

  # Shared box for all machines (can be overridden per machine)
  config.vm.box = "ubuntu/focal64"

  # --- Web Server ---
  config.vm.define "web" do |web|
    web.vm.hostname = "web-server"
    web.vm.network "private_network", ip: "192.168.56.10"

    web.vm.provider "virtualbox" do |vb|
      vb.name   = "web-server"
      vb.memory = "1024"
      vb.cpus   = 1
    end

    web.vm.provision "shell", inline: <<-SHELL
      apt-get update -y
      apt-get install -y nginx
      systemctl enable --now nginx
    SHELL
  end

  # --- Database Server ---
  config.vm.define "db" do |db|
    db.vm.hostname = "db-server"
    db.vm.network "private_network", ip: "192.168.56.11"

    db.vm.provider "virtualbox" do |vb|
      vb.name   = "db-server"
      vb.memory = "2048"
      vb.cpus   = 2
    end

    db.vm.provision "shell", inline: <<-SHELL
      apt-get update -y
      apt-get install -y mysql-server
      systemctl enable --now mysql
    SHELL
  end

end

Multi-Machine Commands

When you have multiple VMs, most commands accept an optional machine name:

1
2
3
4
5
6
7
vagrant up              # Start all machines
vagrant up web          # Start only the web VM
vagrant ssh web         # SSH into the web VM
vagrant ssh db          # SSH into the database VM
vagrant halt db         # Stop only the database VM
vagrant destroy web     # Destroy only the web VM
vagrant status          # Show status of all machines

Dynamic Multi-Machine with Loops

For larger clusters (e.g. worker nodes), you can use Ruby loops instead of repeating blocks:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Vagrant.configure("2") do |config|
  config.vm.box = "ubuntu/focal64"

  # Create 3 worker nodes dynamically
  (1..3).each do |i|
    config.vm.define "worker-#{i}" do |node|
      node.vm.hostname = "worker-#{i}"
      node.vm.network "private_network", ip: "192.168.56.#{10 + i}"

      node.vm.provider "virtualbox" do |vb|
        vb.name   = "worker-#{i}"
        vb.memory = "1024"
        vb.cpus   = 1
      end
    end
  end
end

This defines worker-1, worker-2, and worker-3 at IPs 192.168.56.11–13 with far less code than three explicit blocks.

Tip: Use vagrant up worker-1 to start a single node from a dynamically-defined cluster without booting the rest.


Adding Extra Disks

The default Vagrant box comes with a single disk, but many scenarios — storage servers, database VMs, RAID practice — require additional block devices. The cleanest way to do this with VirtualBox is the vagrant-disksize plugin or direct VBoxManage commands via a trigger.

Method 1: Using vagrant-disksize Plugin

Install the plugin once:

1
vagrant plugin install vagrant-disksize

Then set the disk size in your Vagrantfile:

1
2
3
4
Vagrant.configure("2") do |config|
  config.vm.box = "ubuntu/focal64"
  config.disksize.size = "50GB"   # Resize the primary disk
end

Note: vagrant-disksize resizes the primary disk. After vagrant up, you still need to extend the partition and filesystem inside the VM with growpart and resize2fs.

Method 2: Attach Additional VDI Disks with VBoxManage

For attaching a second disk (separate from the OS disk) — useful for practicing LVM, partitioning, or storage configuration:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
Vagrant.configure("2") do |config|
  config.vm.box = "ubuntu/focal64"
  config.vm.hostname = "storage-server"
  config.vm.network "private_network", ip: "192.168.56.20"

  config.vm.provider "virtualbox" do |vb|
    vb.name   = "storage-server"
    vb.memory = "1024"
    vb.cpus   = 1

    # Path for the extra disk file on your host
    extra_disk = File.join(Dir.home, "VirtualBox VMs", "storage-server-data.vdi")

    # Create the disk only if it doesn't already exist
    unless File.exist?(extra_disk)
      vb.customize ["createmedium", "disk",
                    "--filename", extra_disk,
                    "--size",     "20480",       # 20 GB in MB
                    "--format",   "VDI"]
    end

    # Attach it to the SATA controller as a second drive
    vb.customize ["storageattach", :id,
                  "--storagectl", "SATA Controller",
                  "--port",       1,
                  "--device",     0,
                  "--type",       "hdd",
                  "--medium",     extra_disk]
  end

  # Partition and format the new disk inside the VM
  config.vm.provision "shell", inline: <<-SHELL
    # Wait for the disk to appear
    while [ ! -b /dev/sdb ]; do sleep 1; done

    # Create a partition, format it, and mount it
    echo -e "n\np\n1\n\n\nw" | fdisk /dev/sdb
    mkfs.ext4 /dev/sdb1
    mkdir -p /data
    mount /dev/sdb1 /data

    # Make the mount persistent across reboots
    echo "/dev/sdb1  /data  ext4  defaults  0  2" >> /etc/fstab
    echo "Extra disk mounted at /data"
  SHELL
end

After vagrant up, verify inside the VM:

1
2
lsblk
df -h /data

Advanced Vagrantfile Techniques

Provisioning with an External Shell Script

For complex provisioning, keep scripts in separate files rather than inline heredocs:

1
config.vm.provision "shell", path: "scripts/setup.sh"

Create scripts/setup.sh:

1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash
set -euo pipefail

apt-get update -y
apt-get install -y curl git vim htop

# Create an app user
useradd -m -s /bin/bash appuser
echo "appuser:password" | chpasswd

echo "Setup complete."

Provisioning with Ansible

1
2
3
4
config.vm.provision "ansible" do |ansible|
  ansible.playbook = "provisioning/playbook.yml"
  ansible.verbose  = "v"
end

Port Forwarding

1
2
3
4
# Forward guest port 80 to host port 8080
config.vm.network "forwarded_port", guest: 80,   host: 8080
# Forward guest MySQL port to host port 3306
config.vm.network "forwarded_port", guest: 3306, host: 3306, host_ip: "127.0.0.1"

Synced Folders

1
2
3
4
5
6
7
8
9
# Default synced folder (host current dir → /vagrant in VM)
config.vm.synced_folder ".", "/vagrant"

# Mount a specific host directory with custom permissions
config.vm.synced_folder "./src", "/var/www/html",
  owner: "www-data", group: "www-data"

# NFS mount for better I/O performance on macOS/Linux hosts
config.vm.synced_folder "./data", "/data", type: "nfs"

Environment Variables in Provisioning

1
2
3
4
5
6
7
config.vm.provision "shell" do |s|
  s.env = { "APP_ENV" => "development", "DB_HOST" => "192.168.56.11" }
  s.inline = <<-SHELL
    echo "Environment: $APP_ENV"
    echo "Database: $DB_HOST"
  SHELL
end

Common Issues and Solutions

Error Cause Solution
No usable default provider could be found VirtualBox not installed Install VirtualBox from virtualbox.org and restart
VT-x is not available / AMD-V is disabled Hardware virtualization disabled in BIOS Enter BIOS/UEFI (F2/Del/Esc at boot) and enable Intel VT-x or AMD-V
Port 22 is already in use Another VM using the same port Add config.vm.network "forwarded_port", guest: 22, host: 2223 to remap
Disk space errors Host disk full Run vagrant box list and vagrant box remove to free space
SSH auth method: private key hangs Box download corrupted vagrant box remove ubuntu/focal64 then vagrant up again
Guest additions version mismatch VirtualBox updated since box was built Install vagrant plugin install vagrant-vbguest to auto-update guest additions

Enabling Virtualization in BIOS

If you see VT-x or AMD-V errors:

  1. Restart and press F2, Del, Esc, or F12 during the POST screen to enter BIOS/UEFI.
  2. Navigate to AdvancedCPU Configuration (varies by manufacturer).
  3. Enable Intel Virtualization Technology (VT-x) or SVM Mode (AMD-V).
  4. Save and exit. Retry vagrant up.

Best Practices

Version control your Vagrantfile — commit it to Git so the entire team can reproduce the environment with git clone followed by vagrant up. Add .vagrant/ to .gitignore to avoid committing machine state files.

Use external provisioning scripts rather than long inline heredocs. Scripts in a scripts/ directory are easier to test, lint, and reuse across machines.

Name VMs descriptivelyweb-server-ubuntu, db-postgres-dev, k8s-control-plane communicate purpose instantly. Avoid generic names like vm1 or test.

Keep boxes updated for security patches:

1
vagrant box update

Destroy and recreate instead of patching by hand — if your VM drifts from the Vagrantfile definition, vagrant destroy && vagrant up restores it to a known-good state. This is faster than debugging a modified VM and ensures reproducibility.

Document your Vagrantfile with comments explaining non-obvious choices — IP ranges chosen, memory allocations, why a specific provisioner was used.

Use vagrant validate to check your Vagrantfile for syntax errors before running:

1
vagrant validate

Quick Reference

Command Description
vagrant init BOX Create a new Vagrantfile for the given box
vagrant up Start and provision all VMs
vagrant up NAME Start a specific VM in a multi-machine setup
vagrant ssh SSH into the (only or default) VM
vagrant ssh NAME SSH into a named VM
vagrant halt Gracefully shut down VMs
vagrant suspend Pause VM (saves memory state)
vagrant resume Resume a suspended VM
vagrant reload Restart and re-apply Vagrantfile config
vagrant reload --provision Restart and re-run provisioning
vagrant destroy Delete all VM data permanently
vagrant status Show running state of all VMs
vagrant box list List locally cached boxes
vagrant box update Update boxes to latest versions
vagrant box remove NAME Delete a cached box
vagrant validate Check Vagrantfile syntax
vagrant plugin install NAME Install a Vagrant plugin

Conclusion

Vagrant transforms environment setup from a manual, error-prone process into a version-controlled, repeatable workflow. Whether you are spinning up a single Ubuntu development box, simulating a three-tier web application with multi-machine definitions, or practising storage administration with extra disks, the Vagrantfile gives you a single source of truth for your infrastructure.

The skills you build with Vagrant — writing declarative configuration files, automating provisioning, and managing VM lifecycles — translate directly to tools like Terraform, Ansible, and Docker Compose. It is an excellent entry point into the infrastructure-as-code mindset.

Additional Resources


This post is licensed under CC BY 4.0 by the author.