How to Deploy KVM and Use Libvirt to Create VMs from the CLI

KVM (Kernel-based Virtual Machine) with libvirt is my go-to stack when I want proper virtualization on Linux without the overhead of full GUI tools. If you’re running servers, building a lab, or automating VM provisioning, doing it all through the command line is cleaner, faster, and easier to script.

This guide covers everything from installation to deploying and managing virtual machines entirely from the terminal.

  • Installing KVM and libvirt
  • Verifying hardware virtualization
  • Setting up networking and storage pools
  • Deploying VMs with virt-install
  • Managing VMs via virsh
  • Cloning and templating VMs
  • Automating deployments with scripts

1. Install KVM and Libvirt

1.1 Check Virtualization Support

egrep -c '(vmx|svm)' /proc/cpuinfo

If the output is 1 or higher, your CPU supports hardware virtualization. Then confirm the kernel modules are loaded:

lsmod | grep kvm

1.2 Install on RHEL / Rocky / Alma

sudo dnf install -y \
  qemu-kvm \
  libvirt \
  libvirt-daemon \
  libvirt-daemon-driver-qemu \
  virt-install \
  virt-manager \
  bridge-utils

1.3 Install on Ubuntu / Debian

sudo apt update
sudo apt install -y \
  qemu-kvm \
  libvirt-daemon-system \
  libvirt-clients \
  virtinst \
  bridge-utils

1.4 Enable and Start Libvirt

sudo systemctl enable --now libvirtd
sudo systemctl status libvirtd

If the service is active, KVM and libvirt are ready to go.


2. Verify Setup

2.1 Check Capabilities

virsh capabilities

2.2 List VMs

virsh list --all

2.3 Check Default Network

virsh net-list --all

If the default network exists but isn’t active:

virsh net-start default
virsh net-autostart default

3. Networking Options

There are two main ways to handle networking with libvirt:

  • Use the default NAT network (simpler)
  • Create a Linux bridge for LAN access

3.1 Option A: Default NAT

Libvirt’s built-in NAT network works fine for most setups. VMs will have private IPs and access the internet via NAT through the host. Nothing to configure here.

3.2 Option B: Create a Linux Bridge

For VMs that need to sit directly on your LAN, create a bridge and attach the NIC to it.

Example (RHEL/Rocky/Alma):

# /etc/sysconfig/network-scripts/ifcfg-br0
DEVICE=br0
TYPE=Bridge
BOOTPROTO=dhcp
ONBOOT=yes
# /etc/sysconfig/network-scripts/ifcfg-eno1
DEVICE=eno1
TYPE=Ethernet
BOOTPROTO=none
ONBOOT=yes
BRIDGE=br0
sudo systemctl restart NetworkManager

You can define it in libvirt as well if you want it persistent:

virsh net-define br0.xml
virsh net-start br0
virsh net-autostart br0

4. Storage Pools and Volumes

4.1 Check Default Pool

virsh pool-list --all
virsh pool-start default
virsh pool-autostart default

4.2 Create a Custom Pool

sudo mkdir -p /vm_storage/images

virsh pool-define-as \
  vm_pool dir - - - - "/vm_storage/images"

virsh pool-start vm_pool
virsh pool-autostart vm_pool

4.3 Create a Disk Image

virsh vol-create-as vm_pool rocky8.qcow2 40G --format qcow2

5. Deploy VMs with virt-install

Example 1: Network Install (Rocky Linux)

virt-install \
  --name rocky8 \
  --ram 4096 \
  --vcpus 2 \
  --disk path=/vm_storage/images/rocky8.qcow2,size=40 \
  --os-variant=rocky8 \
  --network network=default \
  --graphics none \
  --location "https://dl.rockylinux.org/pub/rocky/8/BaseOS/x86_64/os/" \
  --extra-args="console=ttyS0,115200n8 serial"

Example 2: Install from ISO

virt-install \
  --name ubuntu-test \
  --ram 4096 \
  --vcpus 2 \
  --disk path=/vm_storage/images/ubuntu-test.qcow2,size=40 \
  --cdrom /isos/ubuntu-22.04.iso \
  --network network=default \
  --graphics vnc \
  --os-variant ubuntu22.04

Example 3: Cloud-Init Image

virt-install \
  --name cloud-ubuntu \
  --ram 2048 \
  --vcpus 2 \
  --disk /vm_storage/images/ubuntu-cloud.qcow2 \
  --cloud-init user-data=cloud-init.yaml \
  --network network=default \
  --os-variant ubuntu22.04 \
  --graphics none

6. Managing VMs with virsh

virsh start rocky8
virsh shutdown rocky8
virsh destroy rocky8   # Force stop
virsh autostart rocky8
virsh console rocky8
virsh list --all

To remove a VM completely:

virsh destroy rocky8
virsh undefine rocky8 --remove-all-storage

7. Managing Networks

Create a new NAT network manually:

<network>
  <name>mynet</name>
  <bridge name='virbr20'/>
  <forward mode='nat'/>
  <ip address='192.168.50.1' netmask='255.255.255.0'>
    <dhcp>
      <range start='192.168.50.10' end='192.168.50.100'/>
    </dhcp>
  </ip>
</network>
virsh net-define mynet.xml
virsh net-start mynet
virsh net-autostart mynet

8. Storage Volume Management

virsh vol-list vm_pool
virsh vol-resize /vm_storage/images/rocky8.qcow2 80G

9. Cloning and Templates

virt-sysprep -d rocky8
virt-clone --original rocky8 --name rocky8-clone --auto-clone

10. Automating Deployments

#!/bin/bash
VM=$1
DISK="/vm_storage/images/${VM}.qcow2"
ISO="/isos/rocky.iso"

virt-install \
  --name "$VM" \
  --ram 2048 \
  --vcpus 2 \
  --disk "$DISK",size=20 \
  --cdrom "$ISO" \
  --network network=default \
  --os-variant rocky8 \
  --graphics none \
  --extra-args="console=ttyS0"

Conclusion

KVM with libvirt gives you a complete virtualization stack that’s fast, stable, and fully automatable. Everything can be controlled from the command line  ideal for headless servers, automation pipelines, and anyone who prefers to keep infrastructure clean and scriptable. Once you’re comfortable with virsh and virt-install, managing dozens of VMs becomes trivial; great open source solution.

Leave a Reply

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

0