Vagrant
02 / 02

Vagrant: Provisioning, Multi-VM Setups & Docker Comparison

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"
end

Multi-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 machine

Vagrant 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