TL;DR

With the VM powered off:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
modprobe nbd max_part=16
qemu-nbd --connect=/dev/nbd0 /var/lib/vz/images/100/vm-100-disk-0.qcow2   # add -r for read-only
partprobe /dev/nbd0
lsblk /dev/nbd0                                  # nbd0p1, nbd0p2…
mount /dev/nbd0p2 /mnt/vm

# ... read, copy, or repair ...

umount /mnt/vm
qemu-nbd --disconnect /dev/nbd0

Without the --disconnect the VM will not start again: QEMU finds the image locked.

Why nbd

A .qcow2 is not a dump of the disk: it has a header, a cluster table, internal snapshots, and sometimes a backing file behind it. losetup does not understand it. qemu-nbd does, and publishes it as a block device through the kernel’s NBD client; from there on it is one more disk for lsblk, mount, fsck, or dd.

max_part matters: without it the kernel does not create the nbd0p1, nbd0p2… nodes. partprobe forces them if they do not show up after --connect.

If the VM disk carries LVM

The norm on Ubuntu Server, RHEL, and Fedora: inside the partition there is a VG. Activate it, mount the LV, and when done deactivate it before disconnecting:

1
2
3
4
5
6
7
8
vgscan
vgchange -ay                                   # activates whatever it finds on nbd0
lvs                                            # ubuntu-vg/ubuntu-lv, rhel/root…
mount /dev/ubuntu-vg/ubuntu-lv /mnt/vm

umount /mnt/vm
vgchange -an ubuntu-vg
qemu-nbd --disconnect /dev/nbd0

Classic trap: the host has a VG with the same name as the VM’s (ubuntu-vg vs ubuntu-vg, pve vs pve). LVM refuses to activate two alike. The tool for that is vgimportclone, which renames the VG on the imported disk without touching the host’s:

1
2
vgimportclone -n vm100-vg /dev/nbd0p3
vgchange -ay vm100-vg

If you would rather change nothing inside the VM, guestmount (below) resolves the LVM on its own and does not touch the host’s LVM.

Windows, NTFS, and internal snapshots

  • NTFS: mount -t ntfs3 /dev/nbd0p3 /mnt/vm (kernel 5.15+) or ntfs-3g. If Windows was left hibernated or with Fast Startup, NTFS only mounts read-only; shut it down properly from inside before writing.
  • qcow2 internal snapshot (qemu-img snapshot -l vm.qcow2): qemu-nbd -r -l before-the-change --connect=/dev/nbd0 vm.qcow2 mounts the state of that snapshot, read-only, without touching the current disk.
  • Other formats: qemu-nbd also speaks vmdk, vhdx, and vdi (-f vhdx if it does not detect it); qemu-img convert -O raw is the alternative if you are going to work on the image for a long time.

guestmount: the same without nbd or root

libguestfs mounts the image over FUSE, detects partitions and LVM on its own, and does not interfere with the host:

1
2
3
4
apt install libguestfs-tools          # dnf install guestfs-tools on Fedora/RHEL
virt-filesystems --long -h --all -a vm.qcow2      # what is inside
guestmount -a vm.qcow2 -i --ro /mnt/vm            # -i: mount the operating system it finds
guestunmount /mnt/vm

Slower than nbd for copying gigabytes, but impossible to leave half closed.

Traps

  • Never with the VM running. Read-write you will corrupt the filesystem for sure; with -r you see an inconsistent state (what the guest holds in cache is not on disk). If you need to look at a live VM, take a snapshot and mount the snapshot.
  • The image stays locked while qemu-nbd is connected: qm start 100 fails with Failed to get "write" lock. qemu-nbd -d /dev/nbd0 releases it; ls /sys/block/nbd*/pid tells which ones are still in use.
  • --disconnect with “device busy”: something is still mounted or a VG is active (vgchange -an). lsof /dev/nbd0* finds it.
  • Proxmox with LVM-thin or ZFS needs no nbd: the VM disk is already a block device (/dev/pve/vm-100-disk-0, /dev/zvol/rpool/data/vm-100-disk-0); kpartx -av on it exposes the partitions. qemu-nbd is for qcow2 on directory and NFS storages.
  • Backing files: an image with a backing file mounts the same; whatever you write goes to the top layer. Do not move the base while it is connected.

See also: convert a physical machine to VHDX and disk import on Proxmox. Reference: qemu-nbd in the QEMU documentation.