Vagrant

Reproducible development environments - VM lifecycle, multi-machine setups, provisioning, and snapshots.

Vagrant Basics

Initialize a Vagrantfile
vagrant init hashicorp/bionic64
Lifecycle commands
# Start VM (downloads box if needed)
vagrant up

# SSH into running VM
vagrant ssh

# Halt VM (graceful shutdown)
vagrant halt

# Destroy VM (remove entirely)
vagrant destroy -f

# Restart VM
vagrant reload

# Re-provision without restart
vagrant provision

# Check status
vagrant status
Current machine states:
default                   running (virtualbox)
Box management
# List installed boxes
vagrant box list

# Add box manually
vagrant box add generic/rocky9

# Update box
vagrant box update

# Remove old box
vagrant box remove hashicorp/bionic64 --box-version 202301.01.0

# Prune old versions
vagrant box prune

MUSCLE MEMORY: vagrant up && vagrant ssh — start and connect.

Vagrantfile Configuration

Minimal Vagrantfile
Vagrant.configure("2") do |config|
  config.vm.box = "generic/rocky9"
  config.vm.hostname = "lab-01"
  config.vm.network "private_network", ip: "192.168.56.10"

  config.vm.provider "virtualbox" do |vb|
    vb.memory = 2048
    vb.cpus   = 2
    vb.name   = "lab-01"
  end
end
Multi-machine Vagrantfile
Vagrant.configure("2") do |config|
  config.vm.box = "generic/rocky9"

  config.vm.define "controller" do |ctrl|
    ctrl.vm.hostname = "controller"
    ctrl.vm.network "private_network", ip: "192.168.56.10"
    ctrl.vm.provider "virtualbox" do |vb|
      vb.memory = 1024
    end
  end

  (1..3).each do |i|
    config.vm.define "node-#{i}" do |node|
      node.vm.hostname = "node-#{i}"
      node.vm.network "private_network", ip: "192.168.56.#{10 + i}"
      node.vm.provider "virtualbox" do |vb|
        vb.memory = 2048
      end
    end
  end
end
Port forwarding and synced folders
Vagrant.configure("2") do |config|
  config.vm.box = "generic/rocky9"

  # Forward guest 80 to host 8080
  config.vm.network "forwarded_port", guest: 80, host: 8080

  # Synced folder
  config.vm.synced_folder "./app", "/opt/app"

  # Disable default share
  config.vm.synced_folder ".", "/vagrant", disabled: true
end

Provisioning

Shell provisioner
Vagrant.configure("2") do |config|
  config.vm.box = "generic/rocky9"

  # Inline shell
  config.vm.provision "shell", inline: <<-SHELL
    dnf update -y
    dnf install -y vim htop
  SHELL

  # External script
  config.vm.provision "shell", path: "scripts/setup.sh"
end
Ansible provisioner
Vagrant.configure("2") do |config|
  config.vm.box = "generic/rocky9"

  config.vm.provision "ansible" do |ansible|
    ansible.playbook = "ansible/site.yml"
    ansible.inventory_path = "ansible/inventory"
    ansible.extra_vars = { env: "dev" }
    ansible.verbose = "v"
  end
end

USE CASE: Vagrant for local dev/test environments. Terraform for production infrastructure.

Snapshots

Save and restore state
# Save named snapshot
vagrant snapshot save clean-state

# List snapshots
vagrant snapshot list

# Restore snapshot
vagrant snapshot restore clean-state

# Delete snapshot
vagrant snapshot delete clean-state

# Quick push/pop (stack-based)
vagrant snapshot push
vagrant snapshot pop

PATTERN: Snapshot before risky changes. Restore instead of re-provisioning.

Quick Reference

Task Command

Initialize Vagrantfile

vagrant init hashicorp/bionic64

Start VM

vagrant up

SSH into VM

vagrant ssh

Halt VM

vagrant halt

Destroy VM

vagrant destroy -f

Show status

vagrant status

Re-provision

vagrant provision

Reload (restart + provision)

vagrant reload --provision

List boxes

vagrant box list

Remove box

vagrant box remove hashicorp/bionic64

Snapshot save

vagrant snapshot save clean-state

Snapshot restore

vagrant snapshot restore clean-state

Global status

vagrant global-status