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
- Visit developer.hashicorp.com/vagrant/downloads and download the Windows installer (
.msi). - Double-click the installer and follow the wizard, accepting defaults.
- Restart your computer after installation.
- 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 destroypermanently 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-1to 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-disksizeresizes the primary disk. Aftervagrant up, you still need to extend the partition and filesystem inside the VM withgrowpartandresize2fs.
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:
- Restart and press F2, Del, Esc, or F12 during the POST screen to enter BIOS/UEFI.
- Navigate to Advanced → CPU Configuration (varies by manufacturer).
- Enable Intel Virtualization Technology (VT-x) or SVM Mode (AMD-V).
- 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 descriptively — web-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
- Vagrant Official Documentation
- Vagrant Cloud — Browse Boxes
- VirtualBox Manual
- HashiCorp Learn — Vagrant Getting Started
- HashiCorp Community Forum
- Vagrant GitHub Repository