Of course! Here is a comprehensive guide to using the Python libvirt API, covering everything from installation and basic connection to managing domains, networks, and storage.

What is libvirt?
libvirt is a C toolkit and daemon (libvirtd) that provides a stable and abstract API to manage platform virtualization technologies. It's not a hypervisor itself; it's a management layer that can control various hypervisors like:
- KVM/QEMU (The most common on Linux)
- Xen
- LXC (Linux Containers)
- VMware (ESXi, Workstation)
- Hyper-V
- Bhyve (on BSD)
The Python libvirt API is a Python wrapper around this C library, allowing you to automate and script the management of virtual machines and other virtualization resources.
Installation
Before you can use the API, you need to install both the libvirt library and the Python bindings.
A. Install the libvirt System Library
This is the core C library that the Python bindings will talk to.

On Debian/Ubuntu:
sudo apt update sudo apt install -y qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils
qemu-kvm: The hypervisor (KVM + QEMU).libvirt-daemon-system: Thelibvirtdservice and its configuration.libvirt-clients: Command-line tools likevirsh.bridge-utils: For creating network bridges.
On CentOS/RHEL/Fedora:
sudo dnf install -y qemu-kvm libvirt libvirt-client virt-install bridge-utils
Start and Enable the Service:
sudo systemctl enable --now libvirtd sudo systemctl status libvirtd
B. Install the Python Bindings
You can install this using pip.

pip install libvirt-python
Connecting to the Hypervisor
The first step in any libvirt script is to establish a connection to the libvirt daemon.
import libvirt
# The URI determines how you connect.
# 'qemu:///system' is the most common for KVM on Linux,
# connecting to the system-wide instance of libvirtd.
try:
conn = libvirt.open('qemu:///system')
except libvirt.libvirtError as e:
print(f'Failed to open connection to qemu:///system: {e}')
exit(1)
if conn is None:
print('Failed to open connection.')
exit(1)
print(f'Successfully connected to Hypervisor. Version: {conn.getVersion()}')
Common Connection URIs:
| URI | Description |
|---|---|
qemu:///system |
Connects to the system-wide libvirtd instance. Manages system-wide VMs. (Most common) |
qemu:///session |
Connects to a user-specific libvirtd instance. Manages VMs for your user account. |
xen:// |
Connects to a Xen hypervisor. |
vmware:// |
Connects to a VMware hypervisor. |
Closing the Connection: Always close the connection when you're done.
conn.close()
Managing Virtual Machines (Domains)
In libvirt terminology, a running or stopped virtual machine is called a domain.
A. Listing All Domains
domains = conn.listAllDomains()
if len(domains) == 0:
print("No domains found.")
else:
print(f"Found {len(domains)} domains:")
for domain in domains:
print(f" - Name: {domain.name()}, ID: {domain.ID()}, State: {domain.state()[0]}")
listAllDomains()returns a list of all defined and running domains.state()returns a tuple. The first element is an integer code for the state (e.g.,libvirt.VIR_DOMAIN_RUNNING,libvirt.VIR_DOMAIN_SHUTOFF).
B. Getting a Specific Domain
You can get a domain by its name or its UUID.
# By Name
domain_name = 'my-vm'
domain = conn.lookupByName(domain_name)
# By UUID (UUID is a string)
domain_uuid = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'
domain = conn.lookupByUUIDString(domain_uuid)
if domain is None:
print(f"Domain '{domain_name}' not found.")
exit(1)
print(f"Found domain: {domain.name()}")
C. Getting Domain Information
The XMLDesc() method is extremely powerful. It returns the full XML definition of the domain, which you can parse or use for backups.
xml_desc = domain.XMLDesc(libvirt.VIR_DOMAIN_XML_SECURE)
print("--- Domain XML Definition ---")
print(xml_desc)
print("-----------------------------")
# Get basic info
print(f"Name: {domain.name()}")
print(f"ID: {domain.ID()}")
print(f"Max Memory (MB): {domain.maxMemory() / 1024 / 1024}")
print(f"Current Memory (MB): {domain.memory() / 1024 / 1024}")
print(f"Number of vCPUs: {domain.vcpuMax()}") # Max vCPUs
D. Lifecycle Management
Create (Start) a Domain from an XML Definition This is useful for starting a VM that is defined but not currently running.
# Define the VM's configuration in XML
# This is a minimal example for a KVM VM
xml_config = """
<domain type='kvm'>
<name>new-vm-from-api</name>
<memory unit='KiB'>4194304</memory>
<currentMemory unit='KiB'>4194304</currentMemory>
<vcpu placement='static'>2</vcpu>
<os>
<type arch='x86_64'>hvm</type>
<boot dev='hd'/>
</os>
<devices>
<disk type='file' device='disk'>
<driver name='qemu' type='qcow2'/>
<source file='/var/lib/libvirt/images/new-vm-from-api.qcow2'/>
<target dev='vda' bus='virtio'/>
</disk>
<interface type='network'>
<source network='default'/>
<model type='virtio'/>
</interface>
<graphics type='vnc' port='-1' autoport='yes'/>
</devices>
</domain>
"""
# Create the domain from the XML
try:
new_domain = conn.createXML(xml_config, 0)
print(f"Successfully created domain: {new_domain.name()}")
except libvirt.libvirtError as e:
print(f"Failed to create domain: {e}")
Start an Existing Domain
If the domain is already defined (e.g., in /etc/libvirt/qemu/), you can just start it.
domain = conn.lookupByName('my-vm')
try:
domain.create()
print(f"Domain '{domain.name()}' started successfully.")
except libvirt.libvirtError as e:
print(f"Failed to start domain: {e}")
Graceful Shutdown (ACPI Power Button) This sends a signal to the guest OS to shut down cleanly.
domain = conn.lookupByName('my-vm')
try:
domain.shutdown()
print(f"Domain '{domain.name()}' is shutting down.")
except libvirt.libvirtError as e:
print(f"Failed to shutdown domain: {e}")
Force Stop (Power Off) This is equivalent to pulling the power cord. Use with caution.
domain = conn.lookupByName('my-vm')
try:
domain.destroy()
print(f"Domain '{domain.name()}' has been forcibly destroyed.")
except libvirt.libvirtError as e:
print(f"Failed to destroy domain: {e}")
Managing Storage
Storage is typically represented as storage pools (directories, LVM volumes, etc.) and storage volumes (disk image files within those pools).
A. Listing Storage Pools
pools = conn.listAllStoragePools()
if not pools:
print("No storage pools found.")
else:
print("Available storage pools:")
for pool in pools:
print(f" - Name: {pool.name()}, Capacity: {pool.info()[1] / (1024**3):.2f} GB")
B. Getting a Storage Pool and its Volumes
pool_name = 'default'
pool = conn.storagePoolLookupByName(pool_name)
if pool is None:
print(f"Storage pool '{pool_name}' not found.")
exit(1)
# Get information about the pool
info = pool.info()
print(f"Pool '{pool.name()}' is in state: {info[0]}, Capacity: {info[1] / (1024**3):.2f} GB")
# List all volumes in the pool
volumes = pool.listAllVolumes()
print("\nVolumes in the pool:")
for vol in volumes:
print(f" - Name: {vol.name()}, Capacity: {vol.info()[1] / (1024**3):.2f} GB")
C. Creating a New Storage Volume (Disk Image)
# Define the volume in XML
vol_xml = """
<volume type='file'>
<name>my-new-disk.qcow2</name>
<capacity unit='G'>20</capacity>
<target>
<path>/var/lib/libvirt/images/my-new-disk.qcow2</path>
<format type='qcow2'/>
</target>
</volume>
"""
try:
# Create the volume
new_vol = pool.createXML(vol_xml, 0)
print(f"Successfully created volume: {new_vol.name()}")
except libvirt.libvirtError as e:
print(f"Failed to create volume: {e}")
Managing Networks
Networks in libvirt are virtualized networks (like bridges or NAT networks).
A. Listing Networks
nets = conn.listAllNetworks()
if not nets:
print("No networks found.")
else:
print("Available networks:")
for net in nets:
print(f" - Name: {net.name()}, Active: {net.isActive()}")
B. Getting Network Information
net = conn.networkLookupByName('default')
if net is None:
print("Network 'default' not found.")
exit(1)
print(f"Network '{net.name()}' is active: {net.isActive()}")
print("--- Network XML ---")
print(net.XMLDesc(0))
print("--------------------")
A Complete Practical Example: Snapshotting a VM
This script connects to a VM, creates a snapshot, and then reverts to it.
import libvirt
import time
# --- Configuration ---
VM_NAME = 'my-vm'
SNAPSHOT_NAME = 'snapshot-before-update'
SNAPSHOT_DESCRIPTION = 'Snapshot taken before a major OS update.'
def main():
# 1. Connect
conn = libvirt.open('qemu:///system')
if conn is None:
print('Failed to open connection to qemu:///system')
return -1
# 2. Get the domain
domain = conn.lookupByName(VM_NAME)
if domain is None:
print(f"Domain '{VM_NAME}' not found.")
conn.close()
return -1
print(f"Working with domain: {domain.name()}")
# 3. Define the snapshot XML
snapshot_xml = f"""
<domainsnapshot>
<name>{SNAPSHOT_NAME}</name>
<description>{SNAPSHOT_DESCRIPTION}</description>
<memory snapshot='internal'/>
<disks>
<disk name='vda' snapshot='external'/>
</disks>
</domainsnapshot>
"""
# 4. Create the snapshot
print(f"Creating snapshot '{SNAPSHOT_NAME}'...")
try:
snapshot = domain.snapshotCreateXML(snapshot_xml, 0)
print(f"Snapshot created successfully: {snapshot.getName()}")
print(f"UUID: {snapshot.getUUIDString()}")
print(f"State: {snapshot.getState()}")
except libvirt.libvirtError as e:
print(f"Failed to create snapshot: {e}")
conn.close()
return -1
# 5. (Optional) List snapshots for the domain
print("\n--- Listing all snapshots for the domain ---")
try:
snapshots = domain.snapshotListNames()
if snapshots:
for snap_name in snapshots:
print(f" - {snap_name}")
else:
print("No snapshots found.")
except libvirt.libvirtError as e:
print(f"Failed to list snapshots: {e}")
# 6. (Simulate some work) - In a real script, you would do something here.
print("\nSimulating some work on the VM...")
time.sleep(5)
# 7. Revert to the snapshot
print(f"\nReverting VM to snapshot '{SNAPSHOT_NAME}'...")
try:
# You need to get the snapshot object by its name to revert to it
snapshot_to_revert = domain.snapshotLookupByName(SNAPSHOT_NAME)
domain.revertToSnapshot(snapshot_to_revert)
print("VM successfully reverted to the snapshot.")
except libvirt.libvirtError as e:
print(f"Failed to revert to snapshot: {e}")
# 8. Cleanup: Delete the snapshot
print(f"\nDeleting snapshot '{SNAPSHOT_NAME}'...")
try:
domain.snapshotDelete(snapshot_to_revert)
print("Snapshot deleted successfully.")
except libvirt.libvirtError as e:
print(f"Failed to delete snapshot: {e}")
# 9. Close connection
conn.close()
print("Connection closed.")
if __name__ == '__main__':
main()
Best Practices and Tips
- Error Handling: Always wrap
libvirtcalls intry...except libvirt.libvirtErrorblocks. ThelibvirtAPI is powerful but can fail in many ways (e.g., VM not found, permission denied, connection lost). - XML is King: The
XMLDesc()method is your best friend. You can define an entire VM, network, or storage volume in XML and then usecreateXML()to instantiate it. This makes your scripts highly declarative and easy to version control. - Permissions: The user running the Python script needs to be in the
libvirtgroup to connect toqemu:///system.sudo usermod -aG libvirt $USER # You may need to log out and log back in for this to take effect.
- Async Operations: For long-running operations like migration or disk resizing,
libvirtprovides asynchronous APIs (e.g.,domainMigrateSetMaxSpeedwith a callback) to avoid blocking your script. - Use
virshfor Testing: Before writing a complex Python script, use thevirshcommand-line tool to perform the actions manually. This helps you understand the correct commands and XML structures needed. For example:virsh list --allvirsh dominfo my-vmvirsh snapshot-create my-vm snapshot.xml
