{"id":2154,"date":"2022-11-18T10:58:42","date_gmt":"2022-11-18T10:58:42","guid":{"rendered":"https:\/\/www.nicktailor.com\/?p=2154"},"modified":"2025-11-18T10:59:07","modified_gmt":"2025-11-18T10:59:07","slug":"how-to-deploy-kvm-and-use-libvirt-to-create-vms-from-the-cli","status":"publish","type":"post","link":"https:\/\/nicktailor.com\/tech-blog\/how-to-deploy-kvm-and-use-libvirt-to-create-vms-from-the-cli\/","title":{"rendered":"How to Deploy KVM and Use Libvirt to Create VMs from the CLI"},"content":{"rendered":"<h1><\/h1>\n<p>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\u2019re running servers, building a lab, or automating VM provisioning, doing it all through the command line is cleaner, faster, and easier to script.<\/p>\n<p>This guide covers everything from installation to deploying and managing virtual machines entirely from the terminal.<\/p>\n<ul>\n<li>Installing KVM and libvirt<\/li>\n<li>Verifying hardware virtualization<\/li>\n<li>Setting up networking and storage pools<\/li>\n<li>Deploying VMs with <code>virt-install<\/code><\/li>\n<li>Managing VMs via <code>virsh<\/code><\/li>\n<li>Cloning and templating VMs<\/li>\n<li>Automating deployments with scripts<\/li>\n<\/ul>\n<hr \/>\n<h2>1. Install KVM and Libvirt<\/h2>\n<h3>1.1 Check Virtualization Support<\/h3>\n<pre><code>egrep -c '(vmx|svm)' \/proc\/cpuinfo\n<\/code><\/pre>\n<p>If the output is 1 or higher, your CPU supports hardware virtualization. Then confirm the kernel modules are loaded:<\/p>\n<pre><code>lsmod | grep kvm\n<\/code><\/pre>\n<h3>1.2 Install on RHEL \/ Rocky \/ Alma<\/h3>\n<pre><code>sudo dnf install -y \\\n  qemu-kvm \\\n  libvirt \\\n  libvirt-daemon \\\n  libvirt-daemon-driver-qemu \\\n  virt-install \\\n  virt-manager \\\n  bridge-utils\n<\/code><\/pre>\n<h3>1.3 Install on Ubuntu \/ Debian<\/h3>\n<pre><code>sudo apt update\nsudo apt install -y \\\n  qemu-kvm \\\n  libvirt-daemon-system \\\n  libvirt-clients \\\n  virtinst \\\n  bridge-utils\n<\/code><\/pre>\n<h3>1.4 Enable and Start Libvirt<\/h3>\n<pre><code>sudo systemctl enable --now libvirtd\nsudo systemctl status libvirtd\n<\/code><\/pre>\n<p>If the service is active, KVM and libvirt are ready to go.<\/p>\n<hr \/>\n<h2>2. Verify Setup<\/h2>\n<h3>2.1 Check Capabilities<\/h3>\n<pre><code>virsh capabilities\n<\/code><\/pre>\n<h3>2.2 List VMs<\/h3>\n<pre><code>virsh list --all\n<\/code><\/pre>\n<h3>2.3 Check Default Network<\/h3>\n<pre><code>virsh net-list --all\n<\/code><\/pre>\n<p>If the default network exists but isn\u2019t active:<\/p>\n<pre><code>virsh net-start default\nvirsh net-autostart default\n<\/code><\/pre>\n<hr \/>\n<h2>3. Networking Options<\/h2>\n<p>There are two main ways to handle networking with libvirt:<\/p>\n<ul>\n<li>Use the default NAT network (simpler)<\/li>\n<li>Create a Linux bridge for LAN access<\/li>\n<\/ul>\n<h3>3.1 Option A: Default NAT<\/h3>\n<p>Libvirt\u2019s 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.<\/p>\n<h3>3.2 Option B: Create a Linux Bridge<\/h3>\n<p>For VMs that need to sit directly on your LAN, create a bridge and attach the NIC to it.<\/p>\n<p>Example (RHEL\/Rocky\/Alma):<\/p>\n<pre><code># \/etc\/sysconfig\/network-scripts\/ifcfg-br0\nDEVICE=br0\nTYPE=Bridge\nBOOTPROTO=dhcp\nONBOOT=yes\n<\/code><\/pre>\n<pre><code># \/etc\/sysconfig\/network-scripts\/ifcfg-eno1\nDEVICE=eno1\nTYPE=Ethernet\nBOOTPROTO=none\nONBOOT=yes\nBRIDGE=br0\n<\/code><\/pre>\n<pre><code>sudo systemctl restart NetworkManager\n<\/code><\/pre>\n<p>You can define it in libvirt as well if you want it persistent:<\/p>\n<pre><code>virsh net-define br0.xml\nvirsh net-start br0\nvirsh net-autostart br0\n<\/code><\/pre>\n<hr \/>\n<h2>4. Storage Pools and Volumes<\/h2>\n<h3>4.1 Check Default Pool<\/h3>\n<pre><code>virsh pool-list --all\n<\/code><\/pre>\n<pre><code>virsh pool-start default\nvirsh pool-autostart default\n<\/code><\/pre>\n<h3>4.2 Create a Custom Pool<\/h3>\n<pre><code>sudo mkdir -p \/vm_storage\/images\n\nvirsh pool-define-as \\\n  vm_pool dir - - - - \"\/vm_storage\/images\"\n\nvirsh pool-start vm_pool\nvirsh pool-autostart vm_pool\n<\/code><\/pre>\n<h3>4.3 Create a Disk Image<\/h3>\n<pre><code>virsh vol-create-as vm_pool rocky8.qcow2 40G --format qcow2\n<\/code><\/pre>\n<hr \/>\n<h2>5. Deploy VMs with virt-install<\/h2>\n<h3>Example 1: Network Install (Rocky Linux)<\/h3>\n<pre><code>virt-install \\\n  --name rocky8 \\\n  --ram 4096 \\\n  --vcpus 2 \\\n  --disk path=\/vm_storage\/images\/rocky8.qcow2,size=40 \\\n  --os-variant=rocky8 \\\n  --network network=default \\\n  --graphics none \\\n  --location \"https:\/\/dl.rockylinux.org\/pub\/rocky\/8\/BaseOS\/x86_64\/os\/\" \\\n  --extra-args=\"console=ttyS0,115200n8 serial\"\n<\/code><\/pre>\n<h3>Example 2: Install from ISO<\/h3>\n<pre><code>virt-install \\\n  --name ubuntu-test \\\n  --ram 4096 \\\n  --vcpus 2 \\\n  --disk path=\/vm_storage\/images\/ubuntu-test.qcow2,size=40 \\\n  --cdrom \/isos\/ubuntu-22.04.iso \\\n  --network network=default \\\n  --graphics vnc \\\n  --os-variant ubuntu22.04\n<\/code><\/pre>\n<h3>Example 3: Cloud-Init Image<\/h3>\n<pre><code>virt-install \\\n  --name cloud-ubuntu \\\n  --ram 2048 \\\n  --vcpus 2 \\\n  --disk \/vm_storage\/images\/ubuntu-cloud.qcow2 \\\n  --cloud-init user-data=cloud-init.yaml \\\n  --network network=default \\\n  --os-variant ubuntu22.04 \\\n  --graphics none\n<\/code><\/pre>\n<hr \/>\n<h2>6. Managing VMs with virsh<\/h2>\n<pre><code>virsh start rocky8\nvirsh shutdown rocky8\nvirsh destroy rocky8   # Force stop\nvirsh autostart rocky8\nvirsh console rocky8\nvirsh list --all\n<\/code><\/pre>\n<p>To remove a VM completely:<\/p>\n<pre><code>virsh destroy rocky8\nvirsh undefine rocky8 --remove-all-storage\n<\/code><\/pre>\n<hr \/>\n<h2>7. Managing Networks<\/h2>\n<p>Create a new NAT network manually:<\/p>\n<pre><code>&lt;network&gt;\n  &lt;name&gt;mynet&lt;\/name&gt;\n  &lt;bridge name='virbr20'\/&gt;\n  &lt;forward mode='nat'\/&gt;\n  &lt;ip address='192.168.50.1' netmask='255.255.255.0'&gt;\n    &lt;dhcp&gt;\n      &lt;range start='192.168.50.10' end='192.168.50.100'\/&gt;\n    &lt;\/dhcp&gt;\n  &lt;\/ip&gt;\n&lt;\/network&gt;\n<\/code><\/pre>\n<pre><code>virsh net-define mynet.xml\nvirsh net-start mynet\nvirsh net-autostart mynet\n<\/code><\/pre>\n<hr \/>\n<h2>8. Storage Volume Management<\/h2>\n<pre><code>virsh vol-list vm_pool\nvirsh vol-resize \/vm_storage\/images\/rocky8.qcow2 80G\n<\/code><\/pre>\n<hr \/>\n<h2>9. Cloning and Templates<\/h2>\n<pre><code>virt-sysprep -d rocky8\nvirt-clone --original rocky8 --name rocky8-clone --auto-clone\n<\/code><\/pre>\n<hr \/>\n<h2>10. Automating Deployments<\/h2>\n<pre><code>#!\/bin\/bash\nVM=$1\nDISK=\"\/vm_storage\/images\/${VM}.qcow2\"\nISO=\"\/isos\/rocky.iso\"\n\nvirt-install \\\n  --name \"$VM\" \\\n  --ram 2048 \\\n  --vcpus 2 \\\n  --disk \"$DISK\",size=20 \\\n  --cdrom \"$ISO\" \\\n  --network network=default \\\n  --os-variant rocky8 \\\n  --graphics none \\\n  --extra-args=\"console=ttyS0\"\n<\/code><\/pre>\n<hr \/>\n<h2>Conclusion<\/h2>\n<p>KVM with libvirt gives you a complete virtualization stack that\u2019s fast, stable, and fully automatable. Everything can be controlled from the command line\u00a0 ideal for headless servers, automation pipelines, and anyone who prefers to keep infrastructure clean and scriptable. Once you\u2019re comfortable with <code>virsh<\/code> and <code>virt-install<\/code>, managing dozens of VMs becomes trivial; great open source solution.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>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\u2019re 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<a href=\"https:\/\/nicktailor.com\/tech-blog\/how-to-deploy-kvm-and-use-libvirt-to-create-vms-from-the-cli\/\" class=\"read-more\">Read More &#8230;<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[138],"tags":[],"class_list":["post-2154","post","type-post","status-publish","format-standard","hentry","category-linux"],"_links":{"self":[{"href":"https:\/\/nicktailor.com\/tech-blog\/wp-json\/wp\/v2\/posts\/2154","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/nicktailor.com\/tech-blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/nicktailor.com\/tech-blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/nicktailor.com\/tech-blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/nicktailor.com\/tech-blog\/wp-json\/wp\/v2\/comments?post=2154"}],"version-history":[{"count":1,"href":"https:\/\/nicktailor.com\/tech-blog\/wp-json\/wp\/v2\/posts\/2154\/revisions"}],"predecessor-version":[{"id":2155,"href":"https:\/\/nicktailor.com\/tech-blog\/wp-json\/wp\/v2\/posts\/2154\/revisions\/2155"}],"wp:attachment":[{"href":"https:\/\/nicktailor.com\/tech-blog\/wp-json\/wp\/v2\/media?parent=2154"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/nicktailor.com\/tech-blog\/wp-json\/wp\/v2\/categories?post=2154"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/nicktailor.com\/tech-blog\/wp-json\/wp\/v2\/tags?post=2154"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}