Duration: 30-45 minutes Difficulty: Beginner Prerequisites: Basic command-line knowledge, VM disk image file
By the end of this tutorial, you will:
# Install Hyper2KVM
pip install hyper2kvm
# Verify installation
> **Note**: After installation, you have two command names:
> - `h2kvmctl` (primary, kubectl-style, recommended)
> - `hyper2kvm` (legacy, backwards compatible)
>
> Both commands are identical. This tutorial uses `h2kvmctl`.
h2kvmctl --version
Expected output:
hyper2kvm version 1.0.0
Ubuntu/Debian:
sudo apt-get update
sudo apt-get install -y \
qemu-utils \
libvirt-clients \
libvirt-daemon-system
Fedora/RHEL:
sudo dnf install -y \
qemu-img \
libvirt-client \
libvirt-daemon
# Check guestfs
guestfish --version
# Check qemu-img
qemu-img --version
# Check virsh (optional)
virsh --version
For this tutorial, we’ll migrate a Windows Server 2019 VM from Hyper-V to KVM.
Hyper-V VMs are typically in:
/var/lib/hyperv/ (Linux Hyper-V)C:\Users\Public\Documents\Hyper-V\Virtual Hard Disks\ (Windows Hyper-V)VMware VMs are typically in:
/vmfs/volumes/datastore1/ (ESXi)Example:
# Copy VM from Windows host to Linux migration server
scp user@hyperv-host:/path/to/windows-server.vhdx /vms/source/
# Or mount network share
mount -t cifs //hyperv-host/vms /mnt/hyperv -o username=admin
cp /mnt/hyperv/windows-server.vhdx /vms/source/
qemu-img info /vms/source/windows-server.vhdx
Expected output:
image: /vms/source/windows-server.vhdx
file format: vhdx
virtual size: 127 GiB (136365211648 bytes)
disk size: 45.2 GiB
cluster_size: 1048576
# migration.yaml
command: local # You can also use "migrate" as an alias
vmdk: /vms/source/windows-server.vhdx
output_dir: /vms/migrated
to_output: windows-server.qcow2
out_format: qcow2
fstab_mode: stabilize-all
regen_initramfs: true
update_grub: true
win_virtio: true
compress: true
verbose: 1
Tip: Use
command: migrateinstead oflocal- both work identically!
h2kvmctl --config migration.yaml
h2kvmctl --cmd local \
--vmdk /vms/source/windows-server.vhdx \
--output-dir /vms/migrated \
--to-output windows-server.qcow2 \
--out-format qcow2 \
--fstab-mode stabilize-all \
--regen-initramfs \
--update-grub \
--win-virtio \
--compress \
--verbose
| Option | Description |
|---|---|
--cmd local |
Process local disk file migration |
--vmdk |
Source VM disk image (VMDK, VHDX, VHD, etc.) |
--output-dir |
Output directory for converted VM |
--to-output |
Output filename |
--out-format qcow2 |
Target format (qcow2, raw, vmdk, etc.) |
--fstab-mode stabilize-all |
Stabilize fstab with UUIDs |
--regen-initramfs |
Regenerate initramfs with virtio drivers |
--update-grub |
Update GRUB bootloader |
--win-virtio |
Inject VirtIO drivers for Windows |
--compress |
Compress output (for qcow2) |
--verbose |
Show detailed progress |
You’ll see output like:
[INFO] Starting migration...
[INFO] Source: /vms/source/windows-server.vhdx (VHDX, 127 GiB)
[INFO] Target: /vms/migrated/windows-server.qcow2 (QCOW2)
[1/7] Converting disk format...
████████████████████████████████ 100% (45.2 GiB)
[2/7] Launching VMCraft...
✓ NBD device connected: /dev/nbd0
✓ Partitions detected: 3
[3/7] Mounting filesystems...
✓ /dev/nbd0p1: EFI System Partition (FAT32)
✓ /dev/nbd0p2: C:\ (NTFS)
✓ /dev/nbd0p3: Recovery (NTFS)
[4/7] Detecting OS...
✓ OS: Windows Server 2019 Standard
✓ Edition: Datacenter
✓ Build: 17763
[5/7] Applying fixes...
✓ Bootloader: Configured for KVM
✓ Network: VirtIO drivers installed
✓ Storage: VirtIO SCSI drivers installed
✓ fstab: Not applicable (Windows)
[6/7] Cleaning up...
✓ Filesystems unmounted
✓ NBD device disconnected
[7/7] Migration complete!
✓ Target: /vms/migrated/windows-server.qcow2
✓ Size: 45.2 GiB compressed to 38.1 GiB
✓ Duration: 8m 23s
Hyper2KVM automatically applies these fixes during migration:
Problem: Hyper-V/VMware bootloader won’t work on KVM Fix: Reconfigures GRUB (Linux) or BCD (Windows) for KVM hardware
Example (Linux):
/etc/default/grub with KVM-compatible settingsExample (Windows):
Problem: Hyper-V/VMware network drivers incompatible with KVM Fix: Installs VirtIO network drivers, configures network interfaces
Example (Linux):
Example (Windows):
Problem: Hyper-V/VMware storage drivers won’t work on KVM Fix: Installs VirtIO SCSI drivers for disk access
Example (Linux):
virtio_scsi to initramfsExample (Windows):
Problem: Device names change (e.g., /dev/sda → /dev/vda) Fix: Converts device names to UUIDs for stability
Example:
# Before migration (unstable)
/dev/sda1 /boot ext4 defaults 0 1
/dev/sda2 / ext4 defaults 0 0
# After migration (stable)
UUID=abc123... /boot ext4 defaults 0 1
UUID=def456... / ext4 defaults 0 0
After migration, verify the converted disk image:
# Check the output file
qemu-img info /vms/migrated/windows-server.qcow2
# Boot test (optional - requires libvirt)
h2kvmctl --cmd local \
--vmdk /vms/migrated/windows-server.qcow2 \
--libvirt-test
# Manual inspection
virt-inspector /vms/migrated/windows-server.qcow2
Look for these in the migration output:
✓ Disk conversion successful
✓ Bootloader configuration updated
✓ Network configuration stabilized
✓ fstab updated with UUIDs
✓ initramfs regenerated with virtio drivers
✓ Output saved to: /vms/migrated/windows-server.qcow2
# Check the converted disk
qemu-img info /vms/migrated/windows-server.qcow2
# Expected output:
# image: /vms/migrated/windows-server.qcow2
# file format: qcow2
# virtual size: 127 GiB
# disk size: 38.1 GiB (compressed)
# Generate libvirt XML
cat > /etc/libvirt/qemu/windows-server.xml <<'EOF'
<domain type='kvm'>
<name>windows-server</name>
<memory unit='GiB'>8</memory>
<vcpu>4</vcpu>
<os>
<type arch='x86_64' machine='pc-q35-6.2'>hvm</type>
<boot dev='hd'/>
</os>
<features>
<acpi/>
<apic/>
<hyperv>
<relaxed state='on'/>
<vapic state='on'/>
<spinlocks state='on' retries='8191'/>
</hyperv>
</features>
<cpu mode='host-passthrough'/>
<clock offset='localtime'>
<timer name='hypervclock' present='yes'/>
</clock>
<devices>
<disk type='file' device='disk'>
<driver name='qemu' type='qcow2' cache='writeback'/>
<source file='/vms/migrated/windows-server.qcow2'/>
<target dev='vda' bus='virtio'/>
</disk>
<interface type='bridge'>
<source bridge='br0'/>
<model type='virtio'/>
</interface>
<console type='pty'/>
<graphics type='vnc' port='-1' autoport='yes'/>
</devices>
</domain>
EOF
# Define VM in libvirt
sudo virsh define /etc/libvirt/qemu/windows-server.xml
# List VMs to verify
sudo virsh list --all
# Start VM
sudo virsh start windows-server
# Check status
sudo virsh dominfo windows-server
Expected output:
Id: 1
Name: windows-server
UUID: abc123-def456-...
OS Type: hvm
State: running
CPU(s): 4
Max memory: 8388608 KiB
Used memory: 8388608 KiB
# Console access
sudo virsh console windows-server
# Or VNC (check port)
sudo virsh vncdisplay windows-server
# Output: :0 (means localhost:5900)
# Connect with VNC client
vncviewer localhost:5900
Linux VM:
# Check IP address
ip addr show
# Test internet connectivity
ping -c 4 8.8.8.8
# Test DNS
ping -c 4 google.com
Windows VM:
# Check IP address
ipconfig /all
# Test internet connectivity
ping 8.8.8.8
# Test DNS
ping google.com
Linux VM:
# Check critical services
systemctl status sshd
systemctl status NetworkManager
# Check all running services
systemctl list-units --type=service --state=running
Windows VM:
# Check critical services
Get-Service | Where-Object {$_.Status -eq "Running"}
# Check specific service
Get-Service WinRM
Symptom: VM starts but doesn’t reach login prompt
Solution:
# Re-run migration with bootloader and initramfs fixes
h2kvmctl --cmd local \
--vmdk /vms/source/windows-server.vhdx \
--output-dir /vms/migrated \
--to-output windows-server.qcow2 \
--update-grub \
--regen-initramfs \
--verbose
Symptom: VM boots but no network access
Solution:
# Re-run with network configuration fixes
h2kvmctl --cmd local \
--vmdk /vms/source/windows-server.vhdx \
--output-dir /vms/migrated \
--to-output windows-server.qcow2 \
--fstab-mode stabilize-all \
--regen-initramfs \
--verbose
Symptom: “No boot device found” or “Disk not detected”
Solution:
bus='virtio'# Re-run with Windows VirtIO driver injection
h2kvmctl --cmd local \
--vmdk /vms/source/windows-server.vhdx \
--output-dir /vms/migrated \
--to-output windows-server.qcow2 \
--win-virtio \
--compress \
--verbose
Congratulations! You’ve completed your first VM migration. Here’s what to explore next:
Time to completion: 30-45 minutes ✅
Next Tutorial: Intermediate Workflows