Creating Virtual Machines in Proxmox the easy way

Well, kind of the easy way. I honestly had better results using this approach than with the Terraform plugins available for Proxmox. The solution is to run QEMU commands directly in the proxmox node’s CLI and use a cloud-init OS image.
The cloud-init images are typically in qcow2 storage format 1. I will be using the latest debian 12 Bookworm image from the official repo.
wget https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-generic-amd64.qcow2
Next, let’s create a virtual machine template with 2gb of memory and 2 cores and set networking to use the default bridge vmbr0.
$ export vm_template_id=1000
$ export vm_template_name="debian-12-vm-template"
$ qm create $vm_template_id --memory 2048 --cores 2 --name $vm_template_name --net0 virtio,bridge=vmbr0
Import the qcow2 image into the virtual machine
$ qm importdisk $vm_template_id debian-12-generic-amd64.qcow2 local-lvm --format qcow2
Set the SCSI controller and use the SCSI disk to allocate a new volume. There is a special syntax for this and more information available here
$ qm set $vm_template_id --scsihw virtio-scsi-pci --scsi0 local-lvm:$vm_template_id-disk-0
Then configure a CD-ROM drive for passing cloud-init info to the VM
$ qm set $vm_template_id --ide2 local-lvm:cloudinit
I like to resize the VM template so by default it uses 25 GB of storage. Please note that this step is not necessary as you can always resize the VM once you clone the template. I believe the template takes up that size in Proxmox which is wasteful. Anyways, it’s a homelab so I don’t really care. Feels good to work with no constraints.
$ qm resize $vm_template_id scsi0 25G
Use the SCSI disk as the default boot disk
$ qm set $vm_template_id --boot order=scsi0
If you have a default SSH key, you can copy it over to the VM template by default. I also like to give my VMs the same default username and password
$ qm set $vm_template_id --sshkey ~/.ssh/mac.pub --ciuser dev --cipassword dev
Before turning the VM into a template, you may want to boot it up and install qemu-guest-agent first which is necessary for proper communication between proxmox and VMs. Skipping this step means you will have to SSH into every VM and manually install it.
Now back to Proxmox. We are ready to turn our VM into a template so it can used as the base for creating other VMs
$ qm template $vm_template_id
The last step is cloning the template and spinning up as many machines as we’d like. I like to run this step in a loop depending on how many VMs I want to create. One handy command is pvesh get /cluster/nextid which returns the next available VM id so we do not have to manually set it.
$ export proxmox_node_name="pve"
$ export vm_node_name_prefix="k8-nodes-"
$ for i in {1..5}; do \
qm clone $vm_template_id `pvesh get /cluster/nextid` -name ${vm_node_name_prefix}${i} -full true -storage local-lvm -target ${proxmox_node_name};
done
I also like to give my VMs a static IP address. The ip field below is the static IP which falls within my home network’s range of assignable IPs. The gw field, 192.168.1.254, is my home router’s address. The --agent 1 flag enables the qemu-guest-agent. I noticed proxmox failed to get the IP address of the VM when the agent is either stopped or not installed.
$ qm set $vm_id --ipconfig0 ip=192.168.1.10/24,gw=192.168.1.254 --agent 1; \
If you are having issues with qemu-guest-agent, then you might have to SSH into the VMs and manually start the agent. Alternatively, you can run this ansible playbook to do just that
---
- hosts: homelab
become: yes
tasks:
- name: Enable and start QEMU Guest Agent
systemd_service:
name: qemu-guest-agent
state: started
enabled: true
Some other handy commands I find helpful for starting and deleting the VMs are:
# Start the VM
$ qm start $vm_id
# Delete the VM
$ qm destroy $vm_id -destroy-unreferenced-disks 1 -purge 1
And that’s it. Have VM-ing!
qcow2 stands for QEMU Copy-On-Write and is a storage format for virtual disk format. That’s really all I know and need to know for now ↩︎