Proxmox qm CLI VM Build, Part 1: Creating a Debian 13 VM and Surviving the Build Loop of Death
Every VM on my Proxmox host so far has gone in through the web GUI. It works, but it hides what’s actually happening: which disk got created where, what the boot order really is, why a VM won’t come up. This series builds a Debian 13 VM entirely from the qm command line instead, one step at a time, so every decision is a command you can see and repeat.
This first post covers checking the host before you build anything, fetching the install ISO without touching the GUI, and creating the VM. It also covers a real bug I handed my own reader partway through: a boot-order mistake that traps a freshly installed VM in its own installer forever. I’m keeping it in, because debugging it live is more useful than pretending the first command I gave was correct.
Part 2 covers the same build again using a cloud-init image instead of the ISO, and compares the two. This post is ISO-only.
Check the host before you build
Two commands worth running before creating anything: how much room is actually free, and what version of Proxmox you’re on (qm behavior can shift slightly between versions).
$ pveversion
pve-manager/9.2.4/5e5ae681198514d4 (running kernel: 7.0.14-6-pve)
Checking storage is where the first gotcha shows up. If you’re logged in as a non-root user, most Proxmox CLI tools fail outright rather than just warning:
$ /usr/sbin/pvesm status
ipcc_send_rec[1] failed: Unknown error -1
ipcc_send_rec[2] failed: Unknown error -1
ipcc_send_rec[3] failed: Unknown error -1
Unable to load access control list: Unknown error -1
That’s not a broken install. pvesm, qm, and friends all talk to pmxcfs, Proxmox’s cluster filesystem, over a socket that an unprivileged user can’t reach. The fix is sudo on everything:
$ sudo /usr/sbin/pvesm status
Name Type Status Total (KiB) Used (KiB) Available (KiB) %
local dir active 98497780 36313624 57134608 36.87%
local-lvm lvmthin active 10384826368 264813072 10120013295 2.55%
Plenty of room on both. local is where ISOs live by default; local-lvm is where VM disks land.
$ free -h
total used free shared buff/cache available
Mem: 125Gi 52Gi 66Gi 59Mi 7.8Gi 73Gi
Swap: 8.0Gi 0B 8.0Gi
$ nproc
40
40 cores, 73GiB available. A Debian VM barely registers against that.
What’s already on the ISO storage
Check before downloading anything. pvesm list shows every ISO already cached on a storage target:
$ sudo /usr/sbin/pvesm list local --content iso
Volid Format Type Size VMID
local:iso/debian-13.4.0-amd64-netinst.iso iso iso 790626304
...
A Debian 13 netinst ISO was already there, version 13.4.0. Current stable at time of writing is 13.6.0. Reusing the stale copy would have been the path of least resistance, and the wrong one. Checking what’s cached and whether it’s still current is worth doing every time, not just the first time.
Fetching the ISO with pvesh, not wget
The GUI’s “Download from URL” button is actually calling a Proxmox API endpoint, and that endpoint is reachable directly from pvesh. Rather than guess the argument names, ask the API what it wants:
$ sudo pvesh usage /nodes/pve/storage/local/download-url --verbose
USAGE: pvesh create /nodes/pve/storage/local/download-url --content <string> --filename <string> --url <string> [OPTIONS]
Download templates, ISO images, OVAs and VM images by using an URL.
--checksum <string>
--checksum-algorithm <md5 | sha1 | sha224 | sha256 | sha384 | sha512>
--compression <string>
--content <import | iso | vztmpl>
--filename <string>
--url https?://.*
--verify-certificates <boolean> (default=1)
With that confirmed, the actual download:
$ sudo pvesh create /nodes/pve/storage/local/download-url --content iso --filename debian-13.6.0-amd64-netinst.iso --url https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-13.6.0-amd64-netinst.iso
downloading https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-13.6.0-amd64-netinst.iso to /var/lib/vz/template/iso/debian-13.6.0-amd64-netinst.iso
...
Length: 791674880 (755M) [application/x-iso9660-image]
Saving to: '/var/lib/vz/template/iso/debian-13.6.0-amd64-netinst.iso.tmp_dwnl.2023862'
...
2026-07-31 09:31:10 (8.66 MB/s) - '/var/lib/vz/template/iso/debian-13.6.0-amd64-netinst.iso.tmp_dwnl.2023862' saved [791674880/791674880]
download of 'https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-13.6.0-amd64-netinst.iso' to '/var/lib/vz/template/iso/debian-13.6.0-amd64-netinst.iso' finished
A download over HTTPS can fail on the first attempt for reasons that have nothing to do with the command itself (a dropped TLS handshake against a mirror, for instance). If that happens, retry once before assuming something’s actually wrong.
Don’t trust a download blind. Debian publishes a checksum file alongside every release, and comparing it against what you actually got takes one line each:
$ curl -s https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/SHA256SUMS | grep debian-13.6.0-amd64-netinst.iso
65273beed27b2df543b68b65630ba525cfbad8df2b12035732b2dff87d6664e7 debian-13.6.0-amd64-netinst.iso
$ sha256sum /var/lib/vz/template/iso/debian-13.6.0-amd64-netinst.iso
65273beed27b2df543b68b65630ba525cfbad8df2b12035732b2dff87d6664e7 /var/lib/vz/template/iso/debian-13.6.0-amd64-netinst.iso
Match. The ISO is good.
Building the VM
$ sudo qm create 103 --name debian13-cli-build --memory 2048 --cores 2 --net0 virtio,bridge=vmbr1v100 --scsihw virtio-scsi-pci --scsi0 local-lvm:8 --ide2 local:iso/debian-13.6.0-amd64-netinst.iso,media=cdrom --boot order=ide2 --ostype l26
Logical volume "vm-103-disk-0" created.
Logical volume pve/vm-103-disk-0 changed.
scsi0: successfully created disk 'local-lvm:vm-103-disk-0,size=8G'
Worth naming what each flag actually did: --scsi0 local-lvm:8 created an 8GB virtio-scsi disk on the thin-LVM pool, --ide2 ...,media=cdrom attached the ISO as a virtual optical drive, and --boot order=ide2 set the boot order. That last flag is where this build went wrong.
The build loop of death
qm start 103, click through the Debian installer over the Proxmox noVNC console, disk gets partitioned, GRUB gets written, install finishes, VM reboots. And boots straight back into the installer. Again. Every time, no matter how many times the install was repeated.
The --boot order=ide2 flag above is the entire bug. Boot order in qm isn’t “try the disk, fall back to the CD” the way a desktop BIOS often behaves. It’s an explicit list of devices to consider, and anything left off that list is never tried at all. The command only ever told Proxmox that the CD-ROM was a bootable device. The disk was never a candidate, so no matter how complete the install was, the VM was always going to boot the ISO again.
The fix is putting the disk in the boot order too, disk first:
$ sudo qm stop 103
$ sudo qm set 103 --boot order=scsi0;ide2
update VM 103: -boot order=scsi0
-bash: ide2: command not found
A second bug, and this one’s the shell’s, not Proxmox’s. The unquoted semicolon in order=scsi0;ide2 ended the command right there, and bash tried to run ide2 as its own program. The value needs quoting:
$ sudo qm set 103 --boot 'order=scsi0;ide2'
update VM 103: -boot order=scsi0;ide2
$ sudo qm start 103
$ sudo qm status 103
status: running
Disk first, ISO kept as a fallback rather than removed. Once the disk actually had a bootloader on it, from the completed install, it took priority and the VM came up into Debian instead of the installer.
Building it again, properly
The install itself had picked up a full desktop environment by default, which is Debian’s netinst tasksel behavior unless you deselect it. Fine for proving the loop was fixed, overkill for a VM whose actual job later in this series is running a DNS resolver. Worth tearing down and rebuilding clean, with the boot-order fix baked into the create command from the start instead of patched on afterward:
$ sudo qm stop 103
$ sudo qm destroy 103
Logical volume "vm-103-disk-0" successfully removed.
$ sudo qm create 103 --name debian13-cli-build --memory 2048 --cores 2 --net0 virtio,bridge=vmbr1v100 --scsihw virtio-scsi-pci --scsi0 local-lvm:8 --ide2 local:iso/debian-13.6.0-amd64-netinst.iso,media=cdrom --boot 'order=scsi0;ide2' --ostype l26
$ sudo qm start 103
This time, in tasksel: desktop environment and GNOME deselected, SSH server and standard system utilities kept. Selecting SSH server at install time matters for a reason beyond convenience. Proxmox’s noVNC console has no clipboard, so typing anything nontrivial into it by hand is painful. Getting SSH running during the install itself means you never have to type a password or a package name into that console at all.
Install finished clean, no loop:
$ ssh anon@<vm-ip>
anon@debian13-cli-build:~$ ip a
2: ens18: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether bc:24:11:f9:b5:fa brd ff:ff:ff:ff:ff:ff
inet 192.168.1.105/24 brd 192.168.1.255 scope global noprefixroute ens18
A working, minimal Debian 13 VM, built without opening the Proxmox GUI once except to click through the installer’s own screens, which is unavoidable for an interactive install regardless of which tool created the VM underneath it.
Part 2 builds the same VM again, this time from a Debian cloud image and cloud-init, with no installer screens at all. Then it’s worth asking what that actually costs you.