Vagrant is one of the developer tools when working with KVM. It enables developers and system administrators to quickly build and manage virtual machine environments in a single workflow. It focuses on automation to lower the time required to bring up your development environment, while ensuring it’s portable, reproducible and disposable.
Vagrant by default support VirtualBox, Hyper-V, and Docker. However, alternative providers can be installed to support other virtualization platforms such as KVM and VMware.
In this section we discuss how you can utilize existing KVM hypervisor to run vagrant boxes and have isolated virtualized environments. But before another provider can be used, it must be installed. These vagtant providers are installed via the Vagrant plugin system.
To have a working vagrant setup with libvirt, the vagrant-libvirt has to be installed configured. Of course Vagrant is the main dependency that we must install before the plugin. Refer to our guide below to setup Vagrant for use with KVM.
Downloading Vagrant boxes for Libvirt
The fastest way to testing vagrant with libvirt is to use existing boxes. Building your own boxes is an option, but what’s the value of there are community boxes in place.
There are two main places you can find vagrant boxes for Libvirt:
See examples provided on downloading boxes of varying Linux distributions.
- Ubuntu
# Ubuntu 24.04
vagrant box add --provider libvirt alvistack/ubuntu-24.04
# Ubuntu 22.04
vagrant box add --provider libvirt alvistack/ubuntu-22.04
# Ubuntu 20.04
vagrant box add --provider libvirt alvistack/ubuntu-20.04
# Ubuntu 18.04
vagrant box add --provider libvirt alvistack/ubuntu-18.04
- Debian
# Debian 12
vagrant box add --provider libvirt alvistack/debian-12
# Debian 11
vagrant box add --provider libvirt alvistack/debian-11
# Debian 10
vagrant box add --provider libvirt alvistack/debian-10
- Rocky Linux
vagrant box add --provider generic/rocky9
vagrant box add --provider generic/rocky8
- Oracle Linux
vagrant box add --provider generic/oracle9
vagrant box add --provider generic/oracle8
- RHEL Linux
vagrant box add --provider generic/rhel9
vagrant box add --provider generic/rhel8
- Fedora
vagrant box add --provider libvirt alvistack/fedora-41
vagrant box add --provider libvirt alvistack/fedora-40
- AlmaLinux
# AmaLinux 9
vagrant box add --provider libvirt alvistack/almalinux-9
# AlmaLinux 8
vagrant box add --provider libvirt alvistack/almalinux-8
- Arch Linux
vagrant box add --provider libvirt generic/arch
- DragonFlyBSD
vagrant box add --provider generic/dragonflybsd
- FreeBSD
vagrant box add --provider generic/freebsd14
vagrant box add --provider generic/freebsd13
- HardenedBSD
vagrant box add --provider generic/hardenedbsd
- OpenBSD
vagrant box add --provider generic/openbsd7
- openSUSE
vagrant box add --provider generic/opensuse15
vagrant box add --provider generic/opensuse42
- NetBSD
vagrant box add --provider generic/netbsd9
- Gentoo
vagrant box add --provider generic/gentoo
List downloaded boxes by running vagrant box list
command:
$ vagrant box list
alvistack/ubuntu-24.04 (libvirt, 20250103.1.1, (amd64))
bento/almalinux-8 (libvirt, 202406.05.0, (amd64))
generic/alma9 (libvirt, 4.3.12, (amd64))
generic/arch (libvirt, 4.3.12, (amd64))
generic/debian12 (libvirt, 4.3.12, (amd64))
Check if there are newer versions available for the boxes downloaded
# Check all boxes
vagrant box outdated --global
# Check single box used in Vagrantfile
vagrant box outdated
Mastering KVM Virtualization - The Ultimate eBook
From home labs to production clouds - master KVM Host management, automating KVM administration using Terraform, Vagrant, and cloud automation. This eBook will enable you to build scalable virtual infrastructure that works whether you're learning at home or deploying enterprise solutions. Get your full copy today
Create virtual machines using vagrant
The easiest way is to create a vagrant machine is using init
subcommand.
Let’s create a test directory to show how this is done.
mkdir /tmp/vagrant && cd /tmp/vagrant
Run vagrant init
to generate a default Vagrantfile
in the current directory.
vagrant init
cat Vagrantfile
This file is the core configuration for managing Vagrant environments. Below are the key components and their purposes, as seen in the default Vagrantfile
:
Vagrant.configure("2") do |config|
– Specifies the Vagrant configuration format version (usually 2, the latest).config.vm.box
– Specifies the base image for the virtual machine (VM). Replacebase
placeholder with a real box name e.ggeneric/rocky9
config.vm.box_check_update
– Controls automatic box updates checkingconfig.vm.provider
– Section for provider specific settings, e,g VirtualBox, LIbvirt(KVM)config.vm.provision
– Configurations that defines how the VM should be provisionedconfig.vm.synced_folder
– Controls sharing of additional folders from host to the guestconfig.vm.network
– Configuring VM networking
References:
- Vagrant Configuration Version – Vagrantfile
- Official vagrant config.vm – Vagrantfile documentation
Creating Vagrantfile
To keep things clean, we are creating a new vagrant projects directory.
mkdir ~/vagrant && cd ~/vagrant
The base configurations for managing vagrant machines are defined in a Vagrantfile
. Create a new one using your preferred text editor.
nano Vagrantfile
Consider the following contents which can be placed in the file to define a vagrant machine.
Vagrant.configure("2") do |config|
config.vm.hostname = "ubuntu24"
config.vm.box = "alvistack/ubuntu-24.04"
config.vm.box_check_update = true
config.vm.provider :libvirt do |libvirt, override|
libvirt.cpu_mode = "host-passthrough"
libvirt.cpus = 2
libvirt.memory = 4096
libvirt.disk_bus = "virtio"
libvirt.disk_driver :cache => "writeback"
libvirt.driver = "kvm"
libvirt.memorybacking :access, :mode => "shared"
libvirt.nested = true
libvirt.nic_model_type = "virtio"
libvirt.video_type = "virtio"
libvirt.storage :file, bus: "virtio", cache: "writeback"
end
end
ubuntu24
is the hostname of the instance createdalvistack/ubuntu-24.04
is the name of the box to be used. If not present it will be downloaded when you runvagrant up
.- A check for box update is set to
true
. libvirt.cpu_mode = "host-passthrough"
– Passes the host’s CPU features directly to the virtual machine for better performance and compatibility.libvirt.cpus
– CPU allocations for the machinelibvirt.memory
– Memory allocated to the machinelibvirt.disk_bus
– defines disk bus to use. We are usingvirtio
, optimizing performance for virtualized disks.libvirt.disk_driver :cache => "writeback"
– Sets the disk driver cache mode towriteback
to enhance disk write performance.libvirt.driver = "kvm"
– Specifies the use of the KVM hypervisor.libvirt.memorybacking :access, :mode => "shared"
– Configures shared memory backing, allowing better memory sharing between VMs and the host.libvirt.nested = true
– Enables nested virtualization, allowing the VM to run additional VMs within it.libvirt.nic_model_type = "virtio"
– Configures the virtual network interface card (NIC) to use thevirtio
model for better performance.libvirt.video_type = "virtio"
– Sets the video device to use virtio for improvedgraphical performance.libvirt.storage :file, bus: "virtio", cache: "writeback"
– Configures the storage to use a file-based image with the virtio bus and writeback cache mode.
Create vagrant machine from Vagrantfile
Validate Vagrantfile
configuration:
$ vagrant validate
Vagrantfile validated successfully.
To bring up the instance run:
vagrant up
Below is output extracted from execution to show you what to expect.
...
==> default: -- Disk driver opts: cache='writeback'
==> default: -- Graphics Type: vnc
==> default: -- Video Type: virtio
==> default: -- Video VRAM: 16384
==> default: -- Video 3D accel: false
==> default: -- Keymap: en-us
==> default: -- TPM Backend: passthrough
==> default: -- Disk(vdb): /var/lib/libvirt/images/vagrant_default-vdb.qcow2, virtio, 10G
==> default: -- INPUT: type=mouse, bus=ps2
==> default: Creating shared folders metadata...
==> default: Updating domain definition due to configuration change
==> default: Starting domain.
==> default: Domain launching with graphics connection settings...
==> default: -- Graphics Port: 5900
==> default: -- Graphics IP: 127.0.0.1
==> default: -- Graphics Password: Not defined
==> default: -- Graphics Websocket: 5700
==> default: Waiting for domain to get an IP address...
==> default: Waiting for machine to boot. This may take a few minutes...
default: SSH address: 192.168.121.108:22
default: SSH username: vagrant
default: SSH auth method: private key
default:
default: Vagrant insecure key detected. Vagrant will automatically replace
default: this with a newly generated keypair for better security.
default:
default: Inserting generated public key within guest...
default: Removing insecure key from the guest if it's present...
default: Key inserted! Disconnecting and reconnecting using new SSH key...
==> default: Machine booted and ready!
==> default: Setting hostname...
==> default: Rsyncing folder: /home/ubuntu/vagrant/ => /vagrant
The VM created on Libvirt will have a name default
. This can be confirmed using status
command option.
$ vagrant status
Current machine states:
default running (libvirt)
Defining custom libvirt domain name
From the output of vagrant status
command, we can see the VM is named default
. If you want to set custom guest name, use config.vm.define
as shown below:
Vagrant.configure("2") do |config|
config.vm.hostname = "webserver.cloudspinx.com"
config.vm.define "webserver" do |webserver|
end
config.vm.box = "alvistack/ubuntu-24.04"
config.vm.box_check_update = true
config.vm.provider :libvirt do |libvirt, override|
libvirt.cpu_mode = "host-passthrough"
libvirt.cpus = 2
libvirt.memory = 4096
libvirt.disk_bus = "virtio"
libvirt.disk_driver :cache => "writeback"
libvirt.driver = "kvm"
libvirt.memorybacking :access, :mode => "shared"
libvirt.nested = true
libvirt.nic_model_type = "virtio"
libvirt.video_type = "virtio"
libvirt.storage :file, bus: "virtio", cache: "writeback"
#override.vm.synced_folder "./", "/vagrant", type: "virtiofs"
end
end
Where:
webserver.cloudspinx.com
is the instance hostname assignedwebserver
is the name of the domain / VM name as seen in KVM
To start the machine, run the command:
vagrant up
Checking status to confirm it has a name assigned
$ vagrant status
Current machine states:
webserver running (libvirt)
Shell Provisioning in Vagrant
A Vagrant shell provisioner allows you to run shell scripts during the provisioning phase to configure the virtual machine.
Here’s an example of using a shell provisioner in a Vagrantfile to update packages and install apache web server and basic PHP tools.
Vagrant.configure("2") do |config|
# Set the hostname for the VM
config.vm.hostname = "webserver.cloudspinx.com"
# Define the VM named "webserver"
config.vm.define "webserver" do |webserver|end
# Specify the base box
config.vm.box = "alvistack/ubuntu-24.04"
config.vm.box_check_update = true
# Configure the provider settings
config.vm.provider :libvirt do |libvirt, override|
libvirt.cpu_mode = "host-passthrough"
libvirt.cpus = 2
libvirt.memory = 4096
libvirt.disk_bus = "virtio"
libvirt.disk_driver :cache => "writeback"
libvirt.driver = "kvm"
libvirt.memorybacking :access, :mode => "shared"
libvirt.nested = true
libvirt.nic_model_type = "virtio"
libvirt.video_type = "virtio"
# Add storage configuration
libvirt.storage :file, bus: "virtio", cache: "writeback"
end
# Provisioning using a shell script
config.vm.provision "shell", inline: <<-SHELL
apt update && apt -yq upgrade
apt -yq install vim apache2 php libapache2-mod-php php-mysql
SHELL
end
Initialize the VM: If this is the first time you’re running the Vagrantfile
, use:
vagrant up
Re-provision the VM: If the VM is already up and you’ve modified the provisioning script, re-run the provisioner:
vagrant provision
The provisioning process will kickoff instanly:
...
==> webserver: Running provisioner: shell...
webserver: Running: inline script
webserver:
webserver: WARNING: apt does not have a stable CLI interface. Use with caution in scripts.
webserver:
webserver: Hit:1 http://downloadcontent.opensuse.org/repositories/home:/alvistack/xUbuntu_24.04 InRelease
webserver: Hit:2 http://archive.ubuntu.com/ubuntu noble InRelease
webserver: Hit:3 http://ppa.launchpad.net/cappelikan/ppa/ubuntu noble InRelease
webserver: Hit:4 http://archive.ubuntu.com/ubuntu noble-updates InRelease
webserver: Hit:5 http://archive.ubuntu.com/ubuntu noble-backports InRelease
webserver: Hit:6 http://security.ubuntu.com/ubuntu noble-security InRelease
webserver: Reading package lists...
webserver: Building dependency tree...
webserver: Reading state information...
webserver: 6 packages can be upgraded. Run 'apt list --upgradable' to see them.
webserver:
webserver: WARNING: apt does not have a stable CLI interface. Use with caution in scripts.
webserver:
webserver: Reading package lists...
webserver: Building dependency tree...
webserver: Reading state information...
webserver: Calculating upgrade...
webserver: The following upgrades have been deferred due to phasing:
webserver: grub-common grub-pc grub-pc-bin grub2-common python3-distupgrade
webserver: ubuntu-release-upgrader-core
webserver: 0 upgraded, 0 newly installed, 0 to remove and 6 not upgraded.
webserver:
webserver: WARNING: apt does not have a stable CLI interface. Use with caution in scripts.
webserver:
webserver: Reading package lists...
webserver: Building dependency tree...
webserver: Reading state information...
webserver: vim is already the newest version (2:9.1.0016-1ubuntu7.5).
webserver: vim set to manually installed.
webserver: apache2 is already the newest version (2.4.58-1ubuntu8.5).
webserver: php is already the newest version (2:8.3+93ubuntu2).
webserver: libapache2-mod-php is already the newest version (2:8.3+93ubuntu2).
webserver: php-mysql is already the newest version (2:8.3+93ubuntu2).
webserver: 0 upgraded, 0 newly installed, 0 to remove and 6 not upgraded.
Login to the instance once it’s created.
vagrant ssh
Vagrant management commands with examples
This section highlights commonly used vagrant commands to help you work more efficiently with Vagrant and Libvirt.
Print vagrant version
Print the current version of installed vagrant
vagrant version
Manage vagrant boxes
Download vagrant box
vagrant box add --provider libvirt <box-name-or-url>
List downloaded boxes
vagrant box list
Delete vagrant box
vagrant box remove <box>
Prune older boxes
vagrant box prune
Update a specific box
# Inf Vagrantfile
vagrant box update
# Named box
vagrant box update --box <box>
Repackage box
vagrant box repackage <name> <provider> <version>
Validate Vagrantfile
Validate the configurations in your Vagrantfile by running:
vagrant validate
Start / provision vagrant machine
Start and provision the vagrant environment
vagrant up
To provision the vagrant machine use
vagrant provision
Display information about machines
Display information about all known Vagrant environments on the machine
vagrant global-status
Connect to machine via SSH
To connect to a single instance machine defined in a Vagrantfile
vagrant ssh
If there are multiple machines, specify the name
vagrant ssh <name|id>
It’s also possible to output OpenSSH valid configuration to connect to the machine
vagrant ssh-config
Suspend vagrant machine
A vagrant machine can be suspended by executing the command:
vagrant suspend
Restart vagrant machine
Restart a vagrant machine and load new Vagrantfile configuration
vagrant reload
Resume suspended machine
To resume a suspended vagrant machine, run:
vagrant resume
Stopping Vagrant machine(s)
vagrant halt
VM snapshots management
Create a VM snapshot
vagrant snapshot save <vm-name> <snapshot-name>
List snapshots
vagrant snapshot list <vm-name>
Restore snapshot
vagrant snapshot restore <vm-name> <snapshot>
Delete snapshot
vagrant snapshot delete <vm-name> <snapshot>
- Destroy vagrant machine(s)
vagrant destroy
Agree to destroy the VM using y
key:
ubuntu24: Are you sure you want to destroy the 'ubuntu24' VM? [y/N] y
==> ubuntu24: Removing domain...
==> ubuntu24: Deleting the machine folder
- Remove vagrant boxe(s)
vagrant box remove <box-name>
- Stop and delete all traces of the vagrant machine
# Gracefully
vagrant destroy --graceful <name>
# Forcefully
vagrant destroy --force <name>