Vagrant: Provisioning, Multi-VM Setups & Docker Comparison
Provisioning: Shell Scripts & Configuration Management
# Simple shell provisioning -- installs whatever the project needs
config.vm.provision "shell", path: "setup.sh"
# Or reuse an existing Ansible playbook -- the SAME playbook used to
# configure real production servers can provision the dev VM too,
# keeping local and production config consistent
config.vm.provision "ansible" do |ansible|
ansible.playbook = "playbook.yml"
endMulti-Machine Vagrantfile
# Define multiple named VMs in one Vagrantfile -- e.g. an app server
# and a separate database server, to more closely mimic production
Vagrant.configure("2") do |config|
config.vm.define "web" do |web|
web.vm.box = "ubuntu/jammy64"
web.vm.network "forwarded_port", guest: 3000, host: 3000
end
config.vm.define "db" do |db|
db.vm.box = "ubuntu/jammy64"
db.vm.provision "shell", path: "install-postgres.sh"
end
end
# `vagrant up` brings up BOTH machines together
# `vagrant ssh web` targets a specific named machineVagrant vs. Docker
Vagrant provisions a full VM with its own OS kernel; Docker containers share the host's kernel and are generally more lightweight and faster to start. Vagrant fits better when full OS-level isolation or kernel-specific behavior genuinely matters; Docker is often preferred for lower resource overhead and faster iteration.
Why Version-Control the Vagrantfile
Committing the Vagrantfile alongside application code means environment changes are tracked and reviewable like any other code change. Adding a new system dependency in the same PR as the code that needs it keeps the environment definition and the code together -- unlike a separately-maintained setup wiki page, which tends to drift out of date.
Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free