Build a Reproducible Multi-Machine Vagrant Stack
Define named VMs, static private IPs, synced folders, and idempotent provisioning in a Vagrantfile for consistent local stacks.
22 Jul 2026, 02:19 UTC

The problem this setup solves
A shared project folder is not the same as a shared runtime. One developer may run the stack on Ubuntu while another uses macOS, and dependency versions, network bindings, and file paths quickly diverge. The useful takeaway is to define the entire local stack in a Vagrantfile: named virtual machines, stable private IP addresses, synced project code, and an idempotent provisioner so running vagrant up produces the same environment on every host.
What you need before starting
- A supported Vagrant installation (2.x series).
- A compatible provider installed and functional, such as VirtualBox or Hyper-V. The provider is the software that creates and runs the virtual machines.
- A base box matching your host architecture. A base box is a pre-built virtual machine image that Vagrant clones for each machine.
- A project directory where the Vagrantfile can reference local files.
Define the machines and private network
Create a Vagrantfile in the project root. This example defines two machines, web and db, each with a static private IP on a host-only network. Private networking lets the VMs reach each other without exposing services to your local area network.
Vagrant.configure('2') do |config|
config.vm.box = 'ubuntu/jammy64'
config.vm.define 'web' do |web|
web.vm.hostname = 'web.local'
web.vm.network 'private_network', ip: '192.168.56.10'
web.vm.synced_folder '.', '/vagrant', type: 'virtualbox'
web.vm.provision 'shell', inline: 'command -v nginx >/dev/null || { apt-get update; apt-get install -y nginx; }'
web.vm.provision 'shell', inline: 'systemctl enable nginx || true'
end
config.vm.define 'db' do |db|
db.vm.hostname = 'db.local'
db.vm.network 'private_network', ip: '192.168.56.11'
db.vm.synced_folder '.', '/vagrant', type: 'virtualbox'
db.vm.provision 'shell', inline: 'command -v psql >/dev/null || { apt-get update; apt-get install -y postgresql; }'
db.vm.provision 'shell', inline: 'systemctl enable postgresql || true'
end
end
Run vagrant up from the project directory as a normal user. Vagrant downloads the base box if needed, starts both VMs, configures the private network, mounts the synced folder, and runs the provisioners. The shell commands above are idempotent: they install packages only when missing, so re-running them does not change an already configured system.
Mount code with synced folders
A synced folder mirrors a host directory inside the guest. In the example, . (the project directory) maps to /vagrant on each VM. Edits made on the host appear inside the guest without manual copying. Performance and permission behavior vary by provider; VirtualBox shared folders are convenient but can be slow for projects with many small files.
Apply idempotent provisioning
A provisioner automates software setup inside the guest. The shell provisioner runs with guest user privileges, typically root. Because provisioners can run again on vagrant provision, write them so repeated execution is safe. Avoid commands that append duplicate configuration lines or create conflicting users on every run.
Verify the running stack
- Run
vagrant status. Both machines should showrunningunder the selected provider. - Run
vagrant ssh web. Inside the guest, confirm networking withping -c 3 192.168.56.11to reach thedbVM over the private network. - List the synced folder with
ls /vagrantand compare file timestamps against the host to confirm synchronization. - Run
vagrant provisionagain. Output should show no package changes or successful idempotent steps, not errors.
Recover from common issues
- Network or synced folder changes: Run
vagrant reloadto restart the VMs and apply updated Vagrantfile settings. - Provisioner drift: Run
vagrant provisionto re-run setup steps without rebuilding the VM. - Broken or inconsistent VM: Run
vagrant destroy -ffollowed byvagrant upto destroy and rebuild from the base box. This changes state and deletes any data stored inside the VM that is not in a synced folder. - Outdated base box: Run
vagrant box updateto refresh the box image when provider compatibility issues appear.
Limitations and pre-flight check
- Static private IPs can collide with your LAN or other VMs. Choose an unused RFC1918 range, such as
192.168.56.0/24, and verify no overlap before starting. - Provider and box versions are version-sensitive. A box built for an older VirtualBox may fail on a newer provider.
- Provisioners that are not idempotent cause configuration drift on repeated runs. Check scripts with
vagrant provisionbefore sharing the Vagrantfile.
Practical check: after vagrant up, run vagrant status and confirm each defined machine shows running. Then SSH into each machine and ping the peer's private IP. If both succeed, the stack is reproducible and ready for local testing.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.