Skip to main content
RunBook Academy

Proxmox VEVII · Shared StorageFile-level backends

Moving disks between storage backends

Advanced⏱ ~30 minqmpctpvesm

What you'll learn

  • Move a VM disk and a container volume between storages, online and offline
  • Predict the format conversion a move performs and what it costs
  • Explain the default --delete 0 behaviour and find the unused volumes it leaves
  • Anticipate a thin disk inflating to its full size on the destination, and prevent it
  • Plan a whole-storage migration, including what does not travel and how to verify

Prerequisites

Verified against Proxmox VE 9.2.4 · Proxmox Backup Server 4.2.5 · Ceph Squid / Tentacle · Debian 13 (Trixie) · Linux kernel 7.0 (PVE 9.2 default) · 2026-08-12

Not yet marked complete on this device.

Choosing a backend is a decision you make once. Moving between backends is the decision you make every time that first one turns out to have been wrong, or the hardware ages out, or the estate grows into something else.

Proxmox VE makes this genuinely easy, which is the source of most of the trouble: the command is short, it usually works, and the three things it does that you did not ask for are all invisible until later.

The commands

For a virtual machine:

qm disk move <vmid> <disk> [<storage>] [OPTIONS]

qm move-disk and qm move_disk are aliases for the same thing. Options that matter: --format (qcow2, raw, vmdk), --delete, --bwlimit, --digest, --target-disk, --target-vmid.

For a container:

pct move-volume <vmid> <volume> [<storage>] [<target-vmid>] [<target-volume>] [OPTIONS]

The volume is a config key — rootfs, mp0, unused3 — rather than a disk name, and the options are --bwlimit, --delete, --digest and --target-digest.

Service impact possiblemove a VM disk to another storage
VMID=141
DISK=scsi0
DEST=shared-rbd

qm config "$VMID" | grep "^${DISK}:"

qm disk move "$VMID" "$DISK" "$DEST" --bwlimit 100000

qm config "$VMID" | grep -E "^${DISK}:|^unused"
Read-only / Safe
$ qm config 141 | grep -E '^scsi0:|^unused'
scsi0: shared-rbd:vm-141-disk-0,size=500G,discard=on
unused0: local-lvm:vm-141-disk-0

The default that keeps the old copy

--delete defaults to 0. The pct reference says it plainly: by default the original is kept as an unused volume entry. qm behaves the same way.

That default is right. A move is a copy, and keeping the source means a failed verification costs you a configuration edit rather than a restore. But it has a consequence that surprises people at exactly the wrong moment.

Read-only / Safefind every unused volume in the cluster
pvesh get /cluster/resources --type vm --output-format json \
| jq -r '.[] | [.node, .type, .vmid] | @tsv' \
| while IFS=$'\t' read -r node type vmid; do
    if [ "$type" = "qemu" ]; then cfg="qemu"; else cfg="lxc"; fi
    pvesh get "/nodes/${node}/${cfg}/${vmid}/config" --output-format json \
      | jq -r --arg v "$vmid" 'to_entries[] | select(.key|startswith("unused")) | [$v, .key, .value] | @tsv'
  done
Data-loss riskremove one unused volume, after verification
VMID=141

qm config "$VMID" | grep '^unused'

qm unlink "$VMID" --idlist unused0 --force 1

pvesm list local-lvm --vmid "$VMID"

Format conversion happens whether you ask or not

A move between storages of different levels converts the image format, because it must.

FromToWhat happens
qcow2 on a directory/NFS storeLVM-thin, RBD, ZFS zvolBecomes a raw block volume
raw on LVM-thinDirectory or NFSBecomes a file — raw by default, qcow2 if you ask
qcow2qcow2 on another file storeStays qcow2

--format lets you choose on file-level destinations. On block-level destinations there is nothing to choose: the backend holds raw volumes.

The consequence to plan for is not the format itself but what the format was providing.

Online, offline, and migration with local disks

Online. For most backends a disk move works with the guest running: PVE mirrors the disk to the destination while the guest keeps writing, then switches over. It is the right default and it is why storage migrations can be done outside a maintenance window.

Offline. With the guest stopped, the copy is a straight transfer. Faster, simpler, and the only option where the backend combination does not support mirroring.

Across nodes at the same time. qm migrate has --with-local-disks for live storage migration and --targetstorage for mapping. That moves a guest to another node and its disks to another storage in one operation — useful when retiring a node and its local storage together, and correspondingly more to go wrong. Do it for a small guest first.

Service impact possiblemove a container volume with the container stopped
CTID=205
DEST=shared-rbd

pct status "$CTID"
pct shutdown "$CTID" --timeout 120

pct move-volume "$CTID" rootfs "$DEST" --bwlimit 100000

pct start "$CTID"
pct config "$CTID" | grep -E '^rootfs|^unused'

Key takeaways

  • qm disk move <vmid> <disk> [<storage>] for VMs — qm move-disk is an alias — and pct move-volume <vmid> <volume> [<storage>] for containers, where the volume is a config key such as rootfs or mp0.
  • --delete defaults to 0, keeping the source as an unusedN entry. That is the right default and it means a migration campaign frees no space until a deliberate cleanup pass.
  • Remove unused volumes with qm unlink --idlist unusedN --force 1 only after verifying the guest on its new storage. There is no undo.
  • Format conversion is implicit: anything moving to a block-level backend becomes raw. --format only applies where the destination is file-level.
  • Snapshots do not travel. Discard them deliberately, or take a PBS backup first — a backup is the only history that survives a storage change.
  • Thin provisioning is a property of the storage, not the disk. Plan capacity from provisioned sizes, trim guests before moving, and confirm discard=on afterwards.
  • Online moves use a QEMU drive mirror and need to converge; a write-heavy guest may need an offline move rather than a bigger bandwidth limit.
  • Use --digest in scripted moves so a concurrent configuration edit aborts the operation instead of racing it.

Knowledge check

Knowledge check · 5 questions

  1. Q1. Sixty guests are moved off an old LVM-thin pool onto Ceph. Every move succeeds, yet the old pool remains at 96% full and the final moves start failing with "no space left". Why?

  2. Q2. A 500 GB qcow2 disk consuming about 40 GB is moved onto an LVM-thin pool with 200 GB free. The move fails at around 40%. What is happening?

  3. Q3. A disk move fails part-way through. Which statements are correct? Select all that apply.

  4. Q4. A guest snapshot history travels with the disk when it is moved to a different storage backend.

  5. Q5. An online move of a write-heavy database disk has been retried three times and failed to complete each time, leaving a partial volume on the destination. What is the right response?

Passing score: 75%. Answers are checked in this browser.