Introduction
Vagrant creates reproducible virtual machine environments. Combined with Ansible as the provisioner, you get local infrastructure that mirrors production — test playbooks, develop roles, and onboard developers with a single vagrant up. This guide covers the Ansible provisioner, multi-machine setups, and using Vagrant as your Ansible testing ground.
Basic Setup
Vagrantfile
# Vagrantfile
Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/noble64"
config.vm.hostname = "webserver"
config.vm.network "private_network", ip: "192.168.56.10"
config.vm.provider "virtualbox" do |vb|
vb.memory = 2048
vb.cpus = 2
end
config.vm.provision "ansible" do |ansible|
ansible.playbook = "site.yml"
ansible.inventory_path = "inventory/vagrant"
end
end
Playbook
# site.yml
---
- name: Configure web server
hosts: all
become: true
roles:
- base
- nginx
- app_deploy
Inventory
# inventory/vagrant
[webservers]
192.168.56.10
[all:vars]
ansible_user=vagrant
ansible_ssh_private_key_file=.vagrant/machines/webserver/virtualbox/private_key
Ansible Local Provisioner
Run Ansible inside the VM (no Ansible needed on host):
config.vm.provision "ansible_local" do |ansible|
ansible.playbook = "site.yml"
ansible.install_mode = "pip"
ansible.pip_install_cmd = "pip3 install"
end
Multi-Machine Environment
Vagrant.configure("2") do |config|
# Web servers
(1..2).each do |i|
config.vm.define "web#{i}" do |web|
web.vm.box = "ubuntu/noble64"
web.vm.hostname = "web#{i}"
web.vm.network "private_network", ip: "192.168.56.#{10 + i}"
web.vm.provider "virtualbox" do |vb|
vb.memory = 1024
end
end
end
# Database server
config.vm.define "db" do |db|
db.vm.box = "ubuntu/noble64"
db.vm.hostname = "db"
db.vm.network "private_network", ip: "192.168.56.20"
db.vm.provider "virtualbox" do |vb|
vb.memory = 2048
end
end
# Load balancer — runs Ansible after all machines are up
config.vm.define "lb" do |lb|
lb.vm.box = "ubuntu/noble64"
lb.vm.hostname = "lb"
lb.vm.network "private_network", ip: "192.168.56.5"
# Provision ALL machines from the last one
lb.vm.provision "ansible" do |ansible|
ansible.playbook = "site.yml"
ansible.limit = "all"
ansible.groups = {
"webservers" => ["web1", "web2"],
"databases" => ["db"],
"loadbalancers" => ["lb"],
"all:vars" => {
"ansible_user" => "vagrant",
"env" => "development"
}
}
end
end
end
Ansible Groups from Vagrant
config.vm.provision "ansible" do |ansible|
ansible.playbook = "site.yml"
ansible.groups = {
"webservers" => ["web1", "web2"],
"databases" => ["db"],
"loadbalancers" => ["lb"],
"production:children" => ["webservers", "databases", "loadbalancers"]
}
ansible.extra_vars = {
env: "vagrant",
debug_mode: true
}
ansible.tags = ENV['ANSIBLE_TAGS'] || ""
ansible.verbose = ENV['ANSIBLE_VERBOSE'] || false
end
Testing Playbooks with Vagrant
Development Workflow
# Create VMs and run playbooks
vagrant up
# Re-run Ansible after changes
vagrant provision
# Run specific tags
ANSIBLE_TAGS=nginx vagrant provision
# SSH into a machine
vagrant ssh web1
# Destroy and rebuild
vagrant destroy -f && vagrant up
Makefile
# Makefile
.PHONY: up provision test destroy
up:
vagrant up
provision:
vagrant provision
test:
vagrant provision
ansible-playbook -i inventory/vagrant tests/integration.yml
destroy:
vagrant destroy -f
reset: destroy up
Vagrant + Molecule
# molecule/vagrant/molecule.yml
---
dependency:
name: galaxy
driver:
name: vagrant
provider:
name: virtualbox
platforms:
- name: ubuntu
box: ubuntu/noble64
memory: 1024
cpus: 1
- name: rocky
box: rockylinux/9
memory: 1024
cpus: 1
provisioner:
name: ansible
verifier:
name: ansible
Synced Folders for Development
config.vm.synced_folder "./app", "/opt/app", type: "rsync",
rsync__exclude: [".git/", "node_modules/"]
# Or NFS for better performance
config.vm.synced_folder "./app", "/opt/app", type: "nfs"
Port Forwarding
config.vm.network "forwarded_port", guest: 80, host: 8080
config.vm.network "forwarded_port", guest: 443, host: 8443
config.vm.network "forwarded_port", guest: 5432, host: 5432
Libvirt Provider (Linux)
config.vm.provider "libvirt" do |lv|
lv.memory = 2048
lv.cpus = 2
lv.driver = "kvm"
end
Environment-Specific Variables
# group_vars/all/vagrant.yml (only loaded in vagrant inventory)
app_debug: true
db_host: 192.168.56.20
lb_backend_servers:
- 192.168.56.11
- 192.168.56.12
ssl_enabled: false
monitoring_enabled: false
Troubleshooting
SSH Connection Issues
# Check SSH config Vagrant generates
vagrant ssh-config
# Test Ansible connection
ansible all -i inventory/vagrant -m ping
Provisioning Failed
# Re-run with verbose output
ANSIBLE_VERBOSE=vvv vagrant provision
# Run specific machine only
vagrant provision web1
Slow Synced Folders
Use rsync or nfs instead of default VirtualBox shared folders:
config.vm.synced_folder ".", "/vagrant", type: "rsync"
Related Articles
Conclusion
Vagrant + Ansible gives you production-like infrastructure on your laptop. Use the ansible provisioner for multi-machine setups with proper inventory groups, ansible_local when Ansible isn't installed on the host, and vagrant provision for iterative development. Combine with Molecule for structured role testing. Every team member gets identical environments with vagrant up.