Stop saying "it works on my machine." Start shipping consistent environments.
Introduction
Every developer has been there: you clone a repo, follow the README, and two hours later you're deep in dependency hell — wrong Python version, missing system library, or some obscure config that only works on macOS Ventura and above.
Vagrant paired with VirtualBox solves this elegantly. You define your environment as code, and anyone on your team — Windows, macOS, or Linux — spins up an identical VM in minutes.
In this guide, you'll go from zero to a fully reproducible development playground, complete with provisioning, shared folders, and port forwarding.
Prerequisites
Before we start, make sure you have:
- A machine with at least 8 GB RAM (4 GB for the VM + your host OS)
- VirtualBox installed → virtualbox.org/wiki/Downloads
- Vagrant installed → developer.hashicorp.com/vagrant/downloads
Verify your installs:
vagrant --version
# Vagrant 2.4.x
VBoxManage --version
# 7.x.x
Step 1 — Initialize Your Project
Create a new directory for your playground and initialize Vagrant inside it:
mkdir my-dev-playground && cd my-dev-playground
vagrant init ubuntu/jammy64
This generates a Vagrantfile in your current directory. The ubuntu/jammy64 box is Ubuntu 22.04 LTS — a solid, well-maintained base image pulled from the Vagrant Cloud.
Step 2 — Configure the Vagrantfile
Open the generated Vagrantfile and replace its contents with this well-commented configuration:
Vagrant.configure("2") do |config|
# ── Base Box ────────────────────────────────────────────────────
config.vm.box = "ubuntu/jammy64"
config.vm.box_check_update = false
# ── Networking ──────────────────────────────────────────────────
# Access your VM's web server at http://localhost:8080 on the host
config.vm.network "forwarded_port", guest: 80, host: 8080
config.vm.network "forwarded_port", guest: 3000, host: 3000 # Node/React
config.vm.network "forwarded_port", guest: 5432, host: 5432 # PostgreSQL
config.vm.network "forwarded_port", guest: 6379, host: 6379 # Redis
# Private network — access VM directly at this IP from the host
config.vm.network "private_network", ip: "192.168.56.10"
# ── Shared Folders ──────────────────────────────────────────────
# Your project folder syncs automatically into the VM
config.vm.synced_folder ".", "/vagrant", type: "virtualbox"
# ── Provider Settings (VirtualBox) ──────────────────────────────
config.vm.provider "virtualbox" do |vb|
vb.name = "dev-playground"
vb.memory = "2048" # 2 GB RAM — adjust as needed
vb.cpus = 2
# Faster DNS resolution inside the VM
vb.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]
end
# ── Provisioning ────────────────────────────────────────────────
# This shell script runs ONCE when you first `vagrant up`
config.vm.provision "shell", inline: <<-SHELL
echo "==> Updating package lists..."
apt-get update -qq
echo "==> Installing core tools..."
apt-get install -y -qq \
git curl wget unzip build-essential \
software-properties-common apt-transport-https
echo "==> Installing Node.js 20.x..."
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
apt-get install -y nodejs
echo "==> Installing Python 3 + pip..."
apt-get install -y python3 python3-pip python3-venv
echo "==> Installing Docker..."
curl -fsSL https://get.docker.com | sh
usermod -aG docker vagrant
echo "==> Installing PostgreSQL..."
apt-get install -y postgresql postgresql-contrib
sudo -u postgres psql -c "ALTER USER postgres PASSWORD 'postgres';"
sudo -u postgres psql -c "CREATE DATABASE devdb;" 2>/dev/null || true
echo "==> Installing Redis..."
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-server
echo "==> All done! Your playground is ready."
SHELL
end
💡 Tip: The
<<-SHELLblock is a Bash heredoc — any valid shell script goes here. You can also point to an external script file withconfig.vm.provision "shell", path: "provision.sh"to keep things tidy.
Step 3 — Boot the VM
vagrant up
The first run will:
- Download the
ubuntu/jammy64base box (~500 MB, cached locally for future use) - Create and configure a VirtualBox VM
- Run the provisioning script
Subsequent vagrant up calls take only 5–10 seconds since the box is already downloaded and provisioned.
Step 4 — Connect to Your VM
vagrant ssh
You're now inside the VM. Your project folder is mounted at /vagrant:
cd /vagrant
ls # same files as your host machine!
Everything you install globally inside the VM is isolated from your host OS. Clean and reproducible.
Step 5 — Verify Your Stack
Once SSH'd in, quickly verify the tools are running:
# Node.js
node --version && npm --version
# Python
python3 --version && pip3 --version
# Docker
docker --version
docker run hello-world
# PostgreSQL
psql -U postgres -h localhost -c "\l"
# Redis
redis-cli ping # should return PONG
From your host machine, you can connect to services using the forwarded ports:
# PostgreSQL from host (use any GUI like TablePlus, DBeaver)
psql -h localhost -p 5432 -U postgres -d devdb
# Redis from host
redis-cli -h localhost -p 6379 ping
Step 6 — Everyday Workflow
| Command | What it does |
|---|---|
vagrant up |
Start the VM |
vagrant ssh |
SSH into the VM |
vagrant halt |
Gracefully shut down the VM |
vagrant reload |
Restart the VM (picks up Vagrantfile changes) |
vagrant provision |
Re-run the provisioning script |
vagrant destroy |
Delete the VM entirely (Vagrantfile stays) |
vagrant snapshot save <name> |
Save a snapshot of the current VM state |
Reprovisioning: If you update the
Vagrantfileprovisioning script, runvagrant reload --provisionto apply changes without destroying the VM.
Step 7 — Share Your Environment with the Team
The beauty of this setup: commit your Vagrantfile (and any provision.sh scripts) to your repository. Anyone with Vagrant + VirtualBox installed runs:
git clone https://github.com/yourorg/your-repo.git
cd your-repo
vagrant up
...and they have an identical environment to yours. No more "works on my machine."
A minimal .gitignore for Vagrant projects:
.vagrant/
*.log
Bonus: Multi-Machine Setup
Vagrant can spin up multiple VMs in a single Vagrantfile — perfect for simulating a microservices or client/server architecture:
Vagrant.configure("2") do |config|
config.vm.define "web" do |web|
web.vm.box = "ubuntu/jammy64"
web.vm.network "private_network", ip: "192.168.56.11"
web.vm.provision "shell", inline: "apt-get update && apt-get install -y nginx"
end
config.vm.define "db" do |db|
db.vm.box = "ubuntu/jammy64"
db.vm.network "private_network", ip: "192.168.56.12"
db.vm.provision "shell", inline: "apt-get update && apt-get install -y postgresql"
end
end
Start all machines with vagrant up, or target one with vagrant up web.
Troubleshooting
VirtualBox kernel module error on Linux:
sudo /sbin/vboxconfig
Slow synced folder performance on macOS:
Consider switching to NFS: config.vm.synced_folder ".", "/vagrant", type: "nfs"
Port already in use:
Change the host: port number in the forwarded_port config, or stop the conflicting service on your host.
"Box not found" error:
Search available boxes at app.vagrantup.com/boxes/search.
Wrapping Up
You now have a fully portable, version-controlled development playground that:
- Runs identically on any OS
- Installs your entire stack automatically on first boot
- Forwards ports so host tools work seamlessly
- Syncs your code files in real time
- Can be destroyed and recreated in minutes
Once you're comfortable with this setup, consider exploring Ansible provisioning for more complex stacks, or Docker-in-Vagrant for a hybrid container + VM workflow.
Happy hacking! 🚀
Found this useful? Drop a ❤️ and share it with a teammate who's still fighting environment issues. Questions or improvements? Leave a comment below!
Top comments (0)