Deploy Proxmox virtual machines using Cloud-init

Due to performance problems with my ESXI homelab I decided to give the open source solution Proxmox a try. One of my goals was to install all my virtual machines with the Cloud-init solution. With Cloud-init it is possible to inject informations such as ssh keys, network information or user profiles in an standarized way at boot time. The benefit of using Cloud-init is the pre-provisioning of necessary configuration items such as a static ip address or a default user with activated ssh public key authentication. Furthermore, this kind of provisioning helps a lot for further automation steps with Ansible or even complete CI/CD pipelines in Gitlab, Jenkins etc. But first, let's start with the VM provisioning steps in Proxmox.
Location of Cloud-Init images
Nowadays, nearly all big Linux distributions offer ready to use Cloud-init images. Here is a short list of mirrors where you can choose the distribution that fits your needs:
- Ubuntu: https://cloud-images.ubuntu.com
- Debian: https://cloud.debian.org/images/cloud/
- CentOs: https://cloud.centos.org/centos/7/images/
Create your template
All commands below have to be executed on the Proxmox server. You can ssh into it or use the shell in the web interface.
Download the image on your Proxmox server
I'm using Ubuntu for my VMs
bash
wget https://cloud-images.ubuntu.com/daily/server/releases/24.04/release/ubuntu-24.04-server-cloudimg-amd64.img
Define your virtual machine as template
bash
qm create 9000 --name "ubuntu-2404-cloudinit-template" --memory 2048 --net0 virtio,bridge=vmbr0
With this command you have created a new virtual machine with the id 9000 (has to be unique in the Proxmox ecosystem), 2 gigabyte of ram and a bridge network using the virtio controller. I took vmbr0 because it is the standard bridge in Proxmox. Feel free to use another one or add additional hardware to your template.
Import the disk image in the local Proxmox storage
bash
qm importdisk 9000 ubuntu-24.04-server-cloudimg-amd64.img local-lvm
The command line utility copies the image in the local Proxmox storage and assigns it to the previously created template VM with the id 9000. Because it is the first disk for the vm, Proxmox is creating a disk with the naming "vm-9000-disk-0".
Configure your virtual machine to use the disk image
bash
qm set 9000 --scsihw virtio-scsi-pci --scsi0 local-lvm:vm-9000-disk-0
Adding the Cloud-init image as CD-Rom to your virtual machine
qm set 9000 --ide2 local-lvm:cloudinit
This is an important step, because it allows you to change the settings I've already mentioned before. Do not set anything else here, because we're using this virtual machine as a template. You can edit the settings after you've cloned the template for use.
Restrict the virtual machine to boot from the Cloud-init image only
bash
qm set 9000 --boot c --bootdisk scsi0Attach a serial console to the virtual machine (this is needed for some Cloud-Init distributions, such as Ubuntu)
bash
qm set 9000 --serial0 socket --vga serial0Finally create a template
bash
qm template 9000Create a virtual machine out of the template
With the template you can clone as many virtual machines as you like and change the Cloud-init parameters for your needs. First we have to clone the template to a new virtual machine:
bash
qm clone 9000 100 --name my-virtual-machineWe created a new virtual machine with the unique id 100 and the name "my-virtual-machine". Now you can change the Cloud-init settings either in the admin ui or with the qm command:
bash
qm set 100 --sshkey ~/.ssh/id_rsa.pub
qm set 123 --ipconfig0 ip=192.168.2.100/24,gw=192.168.2.1With this command you have set a public key for SSH authentication and the static IP 192.168.2.100. We didn't set a user which means Ubuntu is using the default one (ubuntu). That's it! Your Cloud-Init image should now boot up fine with the desired settings.
Next steps
This tutorial is just the beginning. You're now able to use Terraform, Ansible or other automation tools to create "Infrastructure as a code" helping you to ramp up whole datacenters with just a few commands.
But this is another story I have to tell ;-)

