Running Virtual Machines with Vagrant on KVM

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
🔥 TRENDING - Our #1 Selling eBook

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

Only $10 $20
Get Instant Access →

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). Replace base placeholder with a real box name e.g generic/rocky9
  • config.vm.box_check_update – Controls automatic box updates checking
  • config.vm.provider – Section for provider specific settings, e,g VirtualBox, LIbvirt(KVM)
  • config.vm.provision– Configurations that defines how the VM should be provisioned
  • config.vm.synced_folder– Controls sharing of additional folders from host to the guest
  • config.vm.network – Configuring VM networking

References:

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 created
  • alvistack/ubuntu-24.04 is the name of the box to be used. If not present it will be downloaded when you run vagrant 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 machine
  • libvirt.memory – Memory allocated to the machine
  • libvirt.disk_bus – defines disk bus to use. We are using virtio, optimizing performance for virtualized disks.
  • libvirt.disk_driver :cache => "writeback" – Sets the disk driver cache mode to writeback 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 the virtio 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 assigned
  • webserver 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>

Join our Linux and open source community. Subscribe to our newsletter for tips, tricks, and collaboration opportunities!

Recent Post

Unlock the Right Solutions with Confidence

At CloudSpinx, we don’t just offer services - we deliver clarity, direction, and results. Whether you're navigating cloud adoption, scaling infrastructure, or solving DevOps challenges, our seasoned experts help you make smart, strategic decisions with total confidence. Let us turn complexity into opportunity and bring your vision to life.

Leave a Comment

Your email address will not be published. Required fields are marked *

Related Post

Ansible is a powerful automation tool that simplifies managing Linux servers at scale using declarative, desired state configurations. It ensures […]

Vagrant is one of the developer tools when working with KVM. It enables developers and system administrators to quickly build […]

Virt-Lightning is a lightweight virtual machine management tool created to help developers and system administrators to quickly and consistently deploy […]

Let's Connect

Unleash the full potential of your business with CloudSpinx. Our expert solutions specialists are standing by to answer your questions and tailor a plan that perfectly aligns with your unique needs.
You will get a response from our solutions specialist within 12 hours
We understand emergencies can be stressful. For immediate assistance, chat with us now

Contact CloudSpinx today!

Download CloudSpinx Profile

Discover the full spectrum of our expertise and services by downloading our detailed Company Profile. Simply enter your first name, last name, and email address.