Phase 1: KVM VM Creation
Phase 1: KVM VM Creation
Create the RHEL 9 workstation VM on your local KVM infrastructure. All operations via virsh and virt-install — no virt-manager GUI.
Prerequisites (on KVM host)
# Verify KVM is available
lscpu | grep -i virtualization
# Check modules
lsmod | grep kvm
# Verify libvirt running
systemctl is-active libvirtd
# Your user in libvirt group
groups | grep -E '(libvirt|kvm)'
# If not in group:
sudo usermod -aG libvirt,kvm $(whoami) && newgrp libvirt
Storage Pool
# Create dedicated storage directory
sudo mkdir -p /var/lib/libvirt/images/rhel9-workstation
# Create root disk (40GB — LVM will manage partitions)
sudo qemu-img create -f qcow2 /var/lib/libvirt/images/rhel9-workstation/root.qcow2 40G
# Create additional disks for LVM/Stratis labs
sudo qemu-img create -f qcow2 /var/lib/libvirt/images/rhel9-workstation/data1.qcow2 10G
sudo qemu-img create -f qcow2 /var/lib/libvirt/images/rhel9-workstation/data2.qcow2 10G
# Verify all disks created
ls -lh /var/lib/libvirt/images/rhel9-workstation/
Network: Bridge + VLAN Hook
The VM needs a bridged network on your lab VLAN — not NAT. You’ve done this before with your Rocky KVM hosts.
Verify Existing Bridge
# List bridges on KVM host
ip link show type bridge
# List libvirt networks
virsh net-list --all
# Show bridge details (if br0 exists)
ip -4 -o addr show br0 | awk '{print $2, $4}'
VLAN Hook (if needed)
If the VM needs a specific VLAN tag and you’re using the qemu hook pattern from your Rocky KVM builds:
# Check if VLAN hook exists
cat /etc/libvirt/hooks/qemu 2>/dev/null | head -20
# If hook doesn't exist, create it (adjust VLAN ID and bridge to your lab)
sudo tee /etc/libvirt/hooks/qemu << 'HOOKEOF'
#!/bin/bash
# VLAN persistence hook — assigns VMs to correct VLAN on start
# Triggered by libvirt on VM start/stop/migrate events
GUEST_NAME="$1"
ACTION="$2"
case "${GUEST_NAME}" in
rhel9-workstation)
VLAN_ID=10 # Data VLAN
BRIDGE="br0"
;;
*)
exit 0
;;
esac
if [ "${ACTION}" = "started" ]; then
# Find the VM's tap interface
TAP=$(virsh domiflist "${GUEST_NAME}" | awk '/bridge/{print $1}')
if [ -n "${TAP}" ]; then
bridge vlan add dev "${TAP}" vid "${VLAN_ID}" pvid untagged
logger "qemu-hook: ${GUEST_NAME} tap=${TAP} vlan=${VLAN_ID}"
fi
fi
HOOKEOF
sudo chmod +x /etc/libvirt/hooks/qemu
# Restart libvirtd to pick up hook
sudo systemctl restart libvirtd
| Adjust the VLAN ID, bridge name, and VM-to-VLAN mapping to match your lab. If your bridge already trunks the correct VLANs, you may not need the hook — the VM gets a real IP via DHCP on the bridge’s native VLAN. |
Create VM
sudo virt-install \
--name rhel9-workstation \
--ram 8192 \
--vcpus 4 \
--cpu host-passthrough \
--disk path=/var/lib/libvirt/images/rhel9-workstation/root.qcow2,bus=virtio \
--disk path=/var/lib/libvirt/images/rhel9-workstation/data1.qcow2,bus=virtio \
--disk path=/var/lib/libvirt/images/rhel9-workstation/data2.qcow2,bus=virtio \
--os-variant rhel9.0 \
--cdrom ~/isos/rhel-9.*-x86_64-dvd.iso \
--network bridge=br0,model=virtio \
--graphics vnc,listen=0.0.0.0,port=5901 \
--video virtio \
--console pty,target_type=serial \
--boot uefi \
--noautoconsole
Key flags explained:
| Flag | Why |
|---|---|
|
Expose real CPU features to VM (better performance, required for some workloads) |
|
Bridged to lab VLAN with virtio NIC (fastest paravirtualized driver) |
|
VNC for initial install only — accessed via Cockpit VM console or browser |
|
virtio-gpu (better than QXL for modern guests) |
|
Serial console for |
|
UEFI boot (matches modern hardware, GRUB2 with EFI) |
Adjust --network bridge=br0 to match your KVM host bridge. If your VLAN hook handles tagging, the bridge just needs to be a trunk port.
|
Connect to Installer
You need graphical access for the Anaconda installer only (custom LVM partitioning requires the GUI — text mode inst.text doesn’t support it well). Three options:
Option 1: Cockpit VM Console (Recommended — browser-based)
# Enable Cockpit on KVM host if not already
sudo systemctl enable --now cockpit.socket
Navigate to kvm-host:9090 → Virtual Machines → rhel9-workstation → Console. Works in your browser on Hyprland.
Option 2: virt-viewer (GTK app — works on Wayland/Hyprland)
# From your Hyprland workstation
virt-viewer --connect qemu+ssh://user@kvm-host/system rhel9-workstation
Option 3: VNC via browser
# Get VNC port
virsh vncdisplay rhel9-workstation
Connect with any VNC client or noVNC in browser.
Post-Install: Enable Serial Console
After RHEL 9 is installed, enable serial console for virsh console access (terminal-only, no VNC needed):
# On the RHEL 9 VM (via SSH or initial VNC session):
# Enable serial console in GRUB
sudo grubby --update-kernel=ALL --args="console=ttyS0,115200n8"
# Enable serial getty
sudo systemctl enable serial-getty@ttyS0.service
sudo systemctl start serial-getty@ttyS0.service
# Verify
sudo systemctl is-enabled serial-getty@ttyS0.service
After reboot, you can access the VM with:
# From KVM host — pure terminal, no graphics
virsh console rhel9-workstation
# Ctrl+] to exit
VM Management (virsh Reference)
| Task | Command |
|---|---|
List all VMs |
|
Start |
|
Graceful shutdown |
|
Force stop |
|
Reboot |
|
Serial console |
|
Get IP |
|
Get MAC |
|
VM info |
|
CPU/memory stats |
|
Edit XML |
|
Snapshots
| Task | Command |
|---|---|
Create snapshot |
|
List snapshots |
|
Show snapshot info |
|
Revert to snapshot |
|
Delete snapshot |
|
Disk Management
| Task | Command |
|---|---|
List disks |
|
Add disk (hot) |
|
Remove disk |
|
Create disk image |
|
Disk info |
|
Resize disk |
|
Network Management
| Task | Command |
|---|---|
List interfaces |
|
Interface stats |
|
Autostart on boot |
|
Disable autostart |
|
Dump XML config |
|
Delete VM + storage |
|
| Check | Status |
|---|---|
KVM/libvirt verified on host |
[ ] |
Storage pool and disks created (40GB root + 2x 10GB data) |
[ ] |
Network bridge configured (bridged, not NAT) |
[ ] |
VLAN hook in place (if needed) |
[ ] |
VM created with |
[ ] |
Installer accessible (Cockpit console / virt-viewer / VNC) |
[ ] |
Serial console enabled post-install ( |
[ ] |
|
[ ] |
VM set to autostart ( |
[ ] |