Vagrant is a tool for building and managing virtual machine environments in a single workflow. If you have ever spent hours configuring a development environment only to hear “it works on my machine” from a teammate, Vagrant solves that problem. You define your entire environment in a single file — the Vagrantfile — and anyone on your team can spin up an identical setup with one command.
This guide covers Vagrant installation, Vagrantfile configuration, provisioning strategies, networking, multi-machine setups, and production workflows that keep development environments consistent across your entire team.
Prerequisites
- A 64-bit processor with hardware virtualization enabled (VT-x/AMD-V)
- At least 8 GB RAM (4 GB for the host, 4 GB for VMs)
- VirtualBox installed (or another supported provider)
- Basic command-line experience
- Git installed (for sharing Vagrantfiles)
Installing Vagrant and Your First VM
Install Vagrant from the official downloads page. Avoid package manager versions — they are often outdated.
# Verify installation
vagrant --version
# Initialize a new project with Ubuntu 22.04
mkdir my-project && cd my-project
vagrant init ubuntu/jammy64
# Start the VM
vagrant up
# Connect via SSH
vagrant ssh
The first vagrant up downloads the box image (a pre-built VM template) and creates a virtual machine. Subsequent runs start in seconds because the image is cached locally.
Essential Vagrant Commands
vagrant up # Create and start the VM
vagrant ssh # SSH into the running VM
vagrant halt # Gracefully shut down the VM
vagrant reload # Restart VM (applies Vagrantfile changes)
vagrant destroy # Delete the VM completely
vagrant status # Check VM state
vagrant provision # Re-run provisioning scripts
vagrant snapshot save <name> # Save VM state
vagrant snapshot restore <name> # Restore to saved state
Vagrantfile Configuration Deep Dive
The Vagrantfile is Ruby code that defines your VM configuration. Here is a production-ready example:
Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/jammy64"
config.vm.hostname = "dev-server"
# Network configuration
config.vm.network "private_network", ip: "192.168.56.10"
config.vm.network "forwarded_port", guest: 80, host: 8080
config.vm.network "forwarded_port", guest: 3000, host: 3000
# Synced folder (host -> guest)
config.vm.synced_folder "./src", "/var/www/app",
owner: "www-data", group: "www-data"
# Provider-specific settings
config.vm.provider "virtualbox" do |vb|
vb.memory = "2048"
vb.cpus = 2
vb.name = "dev-server"
# Enable nested virtualization
vb.customize ["modifyvm", :id, "--nested-hw-virt", "on"]
end
# Shell provisioning
config.vm.provision "shell", inline: <<-SHELL
apt-get update
apt-get install -y nginx nodejs npm
systemctl enable nginx
SHELL
end
Networking Options
Vagrant supports three networking modes:
| Mode | Use Case | Access |
|---|---|---|
| Forwarded Port | Expose specific guest ports to host | localhost:8080 → guest:80 |
| Private Network | VM-to-VM communication on host-only network | 192.168.56.10 from host and other VMs |
| Public Network | Bridge to physical network | VM gets a LAN IP, accessible by other machines |
For development, private networks are the safest choice. They allow communication between VMs without exposing services to the broader network.
Provisioning Strategies
Vagrant supports multiple provisioning methods. Choose based on your team’s tooling:
Shell Provisioner (Simple)
config.vm.provision "shell", path: "scripts/setup.sh"
Ansible Provisioner (Recommended for Teams)
config.vm.provision "ansible" do |ansible|
ansible.playbook = "provisioning/playbook.yml"
ansible.inventory_path = "provisioning/inventory"
ansible.become = true
end
Docker Provisioner
config.vm.provision "docker" do |d|
d.pull_images "nginx"
d.pull_images "postgres:15"
d.run "nginx", args: "-p 80:80"
end
Real-world scenario: You have a team of 8 developers working on a web application that requires PostgreSQL, Redis, Nginx, and Node.js. Instead of each developer installing these manually (and ending up with different versions), you create a Vagrantfile with Ansible provisioning. A new developer clones the repo, runs vagrant up, and has a working environment in 10 minutes. When you upgrade PostgreSQL from 14 to 15, you update the playbook, and everyone gets the change on their next vagrant provision.
Multi-Machine Environments
Vagrant excels at simulating production architectures locally. Define multiple VMs in a single Vagrantfile:
Vagrant.configure("2") do |config|
# Web server
config.vm.define "web" do |web|
web.vm.box = "ubuntu/jammy64"
web.vm.hostname = "web-server"
web.vm.network "private_network", ip: "192.168.56.10"
web.vm.provider "virtualbox" do |vb|
vb.memory = "1024"
end
web.vm.provision "shell", inline: <<-SHELL
apt-get update && apt-get install -y nginx
SHELL
end
# Database server
config.vm.define "db" do |db|
db.vm.box = "ubuntu/jammy64"
db.vm.hostname = "db-server"
db.vm.network "private_network", ip: "192.168.56.11"
db.vm.provider "virtualbox" do |vb|
vb.memory = "2048"
end
db.vm.provision "shell", inline: <<-SHELL
apt-get update && apt-get install -y postgresql
SHELL
end
# Cache server
config.vm.define "cache" do |cache|
cache.vm.box = "ubuntu/jammy64"
cache.vm.hostname = "cache-server"
cache.vm.network "private_network", ip: "192.168.56.12"
cache.vm.provision "shell", inline: <<-SHELL
apt-get update && apt-get install -y redis-server
sed -i 's/bind 127.0.0.1/bind 0.0.0.0/' /etc/redis/redis.conf
systemctl restart redis
SHELL
end
end
Start specific machines or all at once:
vagrant up web # Start only the web server
vagrant up # Start all machines
vagrant ssh db # SSH into the database server
Vagrant vs Alternatives Comparison
| Feature | Vagrant | Docker | Multipass | Nix/devenv |
|---|---|---|---|---|
| Isolation | Full VM (complete OS) | Container (shared kernel) | Lightweight VM | Process-level |
| Startup Time | 30-120 seconds | 1-5 seconds | 10-30 seconds | Instant |
| Resource Usage | High (full OS per VM) | Low (shared kernel) | Medium | Minimal |
| Multi-OS | Yes (any OS on any host) | Linux containers only* | Ubuntu only | Linux/macOS |
| Provisioning | Shell, Ansible, Chef, Puppet | Dockerfile | Cloud-init | Nix expressions |
| Network Simulation | Excellent (multi-machine) | Good (Docker networks) | Basic | N/A |
| Production Parity | High | Very high | Medium | Low |
| Learning Curve | Low | Medium | Low | High |
*Docker Desktop runs a Linux VM on macOS/Windows, so containers are still Linux.
When to choose Vagrant: Full OS testing, multi-machine architectures, Windows/Linux cross-platform development, or when your production runs on VMs rather than containers.
Gotchas and Edge Cases
-
Synced folder performance on macOS/Windows: VirtualBox shared folders are notoriously slow for large codebases. Use NFS (
type: "nfs") on macOS/Linux or SMB on Windows. For best performance, usersynctype withvagrant rsync-auto. -
Box version pinning: Always pin your box version in the Vagrantfile (
config.vm.box_version = "20231215.0.0"). Unpinned boxes auto-update and can break provisioning scripts. -
Memory allocation: VirtualBox grabs memory at boot, not on demand. If you allocate 4 GB to a VM that idles at 512 MB, you still lose 4 GB from the host. Size VMs conservatively.
-
Port conflicts: If port 8080 is already in use on the host,
vagrant upfails. Useauto_correct: trueon forwarded ports to let Vagrant pick an available port automatically. -
Vagrant and WSL2: Running Vagrant inside WSL2 requires extra configuration. Set
VAGRANT_WSL_ENABLE_WINDOWS_ACCESS=1and ensure VirtualBox is installed on Windows, not inside WSL. -
Stale NFS exports: On macOS, failed
vagrant destroycan leave stale NFS exports in/etc/exports. Clean them withsudo sed -i '' '/# VAGRANT/d' /etc/exports.
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
vagrant up hangs at “Waiting for machine to boot” | VT-x/AMD-V disabled in BIOS | Enable virtualization in BIOS settings |
| SSH connection timeout | Network misconfiguration | Check config.vm.network settings, try vagrant reload |
| Synced folder not updating | VirtualBox Guest Additions outdated | Install vagrant-vbguest plugin: vagrant plugin install vagrant-vbguest |
| ”The box could not be found” | Box name typo or private box | Verify box name on Vagrant Cloud |
| Provisioning fails mid-run | Script error or missing dependency | Fix script, then run vagrant provision (idempotent scripts help) |
Summary
- Vagrant creates reproducible development environments defined in a single Vagrantfile, eliminating “works on my machine” problems
- Provisioning with shell scripts, Ansible, or Docker automates all software installation inside VMs
- Multi-machine setups simulate production architectures locally with separate web, database, and cache servers
- Private networks provide safe VM-to-VM communication without exposing services externally
- Pin box versions and use NFS synced folders to avoid the two most common Vagrant frustrations
- Choose Vagrant over Docker when you need full OS isolation, multi-OS testing, or VM-based production parity