homelab Kubernetes Linux proxmox

How to create a Proxmox Ubuntu cloud-init image

Background for why I wanted to make a Proxmox Ubuntu cloud-init image

I have recently ventured down the path of attempting to learn CI/CD concepts. I have tried docker multiple times and haven’t really enjoyed the nuances any of the times. To me, LXC/LXD containers are far easier to understand than Docker when coming from a ‘one VM per service’ background. LXC/LXD containers can be assigned IP addresses (or get them from DHCP) and otherwise behave basically exactly like a VM from a networking perspective. Docker’s networking model is quite a bit more nuanced. Lots of people say it’s easier, but having everything run on ‘localhost:[high number port]’ doesn’t work well when you’ve got lots of services, unless you do some reverse proxying, like with Traefik or similar. Which is another configuration step.

It is so much easier to just have a LXC get an IP via DHCP and then it’s accessible from hostname right off the bat (I use pfSense for DHCP/DNS – all DHCP leases are entered right into DNS). Regardless, I know Kubernetes is the new hotness so I figured I need to learn it. Every tutorial says you need a master and at least two worker nodes. No sense making three separate virtual machines – let’s use the magic of virtualization and clone some images! I plan on using Terraform to deploy the virtual machines for my Kubernetes cluster (as in, I’ve already used this Proxmox Ubuntu cloud-init image to make my own Kubernetes nodes but haven’t documented it yet).


The quick summary for this tutorial is:

  1. Download a base Ubuntu cloud image
  2. Install some packages into the image
  3. Create a Proxmox VM using the image
  4. Convert it to a template
  5. Clone the template into a full VM and set some parameters
  6. Automate it so it runs on a regular basis (extra credit)?
  7. ???
  8. Profit!

Youtube Video Link

If you prefer video versions to follow along, please head on over to for a live action video of me creating the Proxmox Ubuntu cloud-init image and why we’re running each command.

#1 – Downloading the base Ubuntu image

Luckily, Ubuntu (my preferred distro, guessing others do the same) provides base images that are updated on a regular basis – We are interested in the “current” release of Ubuntu 20.04 Focal, which is the current Long Term Support version. Further, since Proxmox uses KVM, we will be pulling that image:


#2 – Install packages

It took me quite a while into my Terraform debugging process to determine that qemu-guest-agent wasn’t included in the cloud-init image. Why it isn’t, I have no idea. Luckily there is a very cool tool that I just learned about that enables installing packages directly into a image. The tool is called virt-customize and it comes in the libguestfs-tools package (“libguestfs is a set of tools for accessing and modifying virtual machine (VM) disk images” –

Install the tools:

sudo apt update -y && sudo apt install libguestfs-tools -y

Then install qemu-guest-agent into the newly downloaded image:

sudo virt-customize -a focal-server-cloudimg-amd64.img --install qemu-guest-agent

At this point you can presumably install whatever else you want into the image but I haven’t tested installing other packages. qemu-guest-agent was what I needed to get the VM recognized by Terraform and accessible.

Update 2021-12-30 – it is possible to inject the SSH keys into the cloud image itself before turning it into a template and VM. You need to create a user first and the necessary folders:

# not quite working yet. skip this and continue
#sudo virt-customize -a focal-server-cloudimg-amd64.img --run-command 'useradd austin'
#sudo virt-customize -a focal-server-cloudimg-amd64.img --run-command 'mkdir -p /home/austin/.ssh'
#sudo virt-customize -a focal-server-cloudimg-amd64.img --ssh-inject austin:file:/home/austin/.ssh/
#sudo virt-customize -a focal-server-cloudimg-amd64.img --run-command 'chown -R austin:austin /home/austin'

#3 – Create a Proxmox virtual machine using the newly modified image

The commands here should be relatively self explanatory but in general we are creating a VM (VMID=9000, basically every other resource I saw used this ID so we will too) with basic resources (2 cores, 2048MB), assigning networking to a virtio adapter on vmbr0, importing the image to storage (your storage here will be different if you’re not using ZFS, probably either ‘local’ or ‘local-lvm’), setting disk 0 to use the image, setting boot drive to disk, setting the cloud init stuff to ide2 (which is apparently appears as a CD-ROM to the VM, at least upon inital boot), and adding a virtual serial port. I had only used qm to force stop VMs before this but it’s pretty useful.

sudo qm create 9000 --name "ubuntu-2004-cloudinit-template" --memory 2048 --cores 2 --net0 virtio,bridge=vmbr0
sudo qm importdisk 9000 focal-server-cloudimg-amd64.img local-zfs
sudo qm set 9000 --scsihw virtio-scsi-pci --scsi0 local-zfs:vm-9000-disk-0
sudo qm set 9000 --boot c --bootdisk scsi0
sudo qm set 9000 --ide2 local-zfs:cloudinit
sudo qm set 9000 --serial0 socket --vga serial0
sudo qm set 9000 --agent enabled=1

You can start the VM up at this point if you’d like and make any other changes you want because the next step is converting it to a template. If you do boot it, I will be completely honest I have no idea how to log into it. I actually just googled this because I don’t want to leave you without an answer – looks like you can use the same virt-customize we used before to set a root password according to stackoverflow ( Not going to put that into a command window here because cloud-init is really meant for public/private key authentication (see post here for a quick SSH tutorial).

#4 – Convert VM to a template

Ok if you made any changes, shut down the VM. If you didn’t boot the VM, that’s perfectly fine also. We need to convert it to a template:

sudo qm template 9000

And now we have a functioning template!

screenshot of proxmox ui showing ubuntu 20.04 cloud-init template

#5 – Clone the template into a full VM and set some parameters

From this point you can clone the template as much as you want. But, each time you do so it makes sense to set some parameters, namely the SSH keys present in the VM as well as the IP address for the main interface. You could also add the SSH keys with virt-customize but I like doing it here.

First, clone the VM (here we are cloning the template with ID 9000 to a new VM with ID 999):

sudo qm clone 9000 999 --name test-clone-cloud-init

Next, set the SSH keys and IP address:

sudo qm set 999 --sshkey ~/.ssh/
sudo qm set 999 --ipconfig0 ip=,gw=

It’s now ready to start up!

sudo qm start 999

You should be able to log in without any problems (after trusting the SSH fingerprint). Note that the username is ‘ubuntu’, not whatever the username is for the key you provided.

ssh [email protected]

Once you’re happy with how things worked, you can stop the VM and clean up the resources:

sudo qm stop 999 && sudo qm destroy 999
rm focal-server-cloudimg-amd64.img

#6 – automating the process

I have not done so yet, but if you create VMs on a somewhat regular basis, it wouldn’t be hard to stick all of the above into a simple shell script (update 2022-04-19: simple shell script below) and run it via cron on a weekly basis or whatever frequency you prefer. I can’t tell you how many times I make a new VM from whatever .iso I downloaded and the first task is apt upgrade taking forever to run (‘sudo apt update’ –> “176 packages can be upgraded”). Having a nice template always ready to go would solve that issue and would frankly save me a ton of time.

#6.5 – Shell script to create template

# installing libguestfs-tools only required once, prior to first run
sudo apt update -y
sudo apt install libguestfs-tools -y

# remove existing image in case last execution did not complete successfully
rm focal-server-cloudimg-amd64.img
sudo virt-customize -a focal-server-cloudimg-amd64.img --install qemu-guest-agent
sudo qm create 9000 --name "ubuntu-2004-cloudinit-template" --memory 2048 --cores 2 --net0 virtio,bridge=vmbr0
sudo qm importdisk 9000 focal-server-cloudimg-amd64.img local-zfs
sudo qm set 9000 --scsihw virtio-scsi-pci --scsi0 local-zfs:vm-9000-disk-0
sudo qm set 9000 --boot c --bootdisk scsi0
sudo qm set 9000 --ide2 local-zfs:cloudinit
sudo qm set 9000 --serial0 socket --vga serial0
sudo qm set 9000 --agent enabled=1
sudo qm template 9000
rm focal-server-cloudimg-amd64.img
echo "next up, clone VM, then expand the disk"
echo "you also still need to copy ssh keys to the newly cloned VM"

#7-8 – Using this template with Terraform to automate VM creation

Next post – How to deploy VMs in Proxmox with Terraform


My original notes

# create cloud image VM
sudo qm create 9000 --name "ubuntu-2004-cloudinit-template" --memory 2048 --cores 2 --net0 virtio,bridge=vmbr0

# to install qemu-guest-agent or whatever into the guest image
#sudo apt-get install libguestfs-tools
#virt-customize -a focal-server-cloudimg-amd64.img --install qemu-guest-agent
sudo qm importdisk 9000 focal-server-cloudimg-amd64.img local-zfs
sudo qm set 9000 --scsihw virtio-scsi-pci --scsi0 local-zfs:vm-9000-disk-0
sudo qm set 9000 --boot c --bootdisk scsi0
sudo qm set 9000 --ide2 local-zfs:cloudinit
sudo qm set 9000 --serial0 socket --vga serial0
sudo qm template 9000

# clone cloud image to new VM
sudo qm clone 9000 999 --name test-clone-cloud-init
sudo qm set 999 --sshkey ~/.ssh/
sudo qm set 999 --ipconfig0 ip=,gw=
sudo qm start 999
# remove known host because SSH key changed
ssh-keygen -f "/home/austin/.ssh/known_hosts" -R ""

# ssh in
ssh -i ~/.ssh/id_rsa [email protected]

# stop and destroy VM
sudo qm stop 999 && sudo qm destroy 999

22 replies on “How to create a Proxmox Ubuntu cloud-init image”

I must have qemu-guest-agent enabled by default somewhere because they all have it already. Regardless, this is a good idea so I’ll add it. Thanks for the comment!

Great posts, however I made some observations myself that I wanted to share:
You can indeed boot the vm and install custom packages, however if you boot the vm the first it gets a machine-id assigned (/etc/machine-id) which is used for dhcp request (at least with opnsense in my case), therefore if you clone the machine id stays the same and you get the same ip. After all thats the reason why I need searched on how to add guest agent without booting it first. Maybe you can reset the machine id, has probably something todo with the cloud-init first boot stage, but haven’t further investigated because installing guest-agent was enough for me.

Secondly if you do want to boot the vm and login you can provide a password in the vms cloud-init tab in proxmox ui, because the cloud img has cloud init enabled. Might be useful for quick debugging.

I have installed libeguestfs on Proxmox but when running below command:
virt-customize -a focal-server-cloudimg-amd64.img –install qemu-guest-agent

Getting below error:
[email protected]:~# virt-customize -a focal-server-cloudimg-amd64.img –install qemu-guest-agent
[ 0.0] Examining the guest …
virt-customize: error: libguestfs error: /usr/bin/qemu-system-x86_64 killed
by signal 11 (Segmentation fault).
To see full error messages you may need to enable debugging.
and run the command again. For further information, read:
You can also run ‘libguestfs-test-tool’ and post the *complete* output
into a bug report or message to the libguestfs mailing list.

If reporting bugs, run virt-customize with debugging enabled and include
the complete output:

virt-customize -v -x […]
[email protected]:~#

Hi, great article, I’m learning tons.

One little thing I noticed is the “qm set … –scsi0” is wrong.

I had to do this instead:
qm set 9000 –scsihw virtio-scsi-pci –scsi0 ext8tb1:9000/vm-9000-disk-0.raw

Obviously, my storage is different, but I had to add the path to full file as reported by the importdisk command

It’s the storage type you used. I literally ran through these commands a few days ago and everything worked fine for me. I even adjusted the agent command to fix an issue.

Here’s my bash history list that worked:
29 sudo qm create 9000 –name “ubuntu-2004-cloudinit-template” –memory 2048 –cores 2 –net0 virtio,bridge=vmbr0
30 sudo qm importdisk 9000 focal-server-cloudimg-amd64.img local-zfs
31 sudo qm set 9000 –scsihw virtio-scsi-pci –scsi0 local-zfs:vm-9000-disk-0
32 sudo qm set 9000 –boot c –bootdisk scsi0
33 sudo qm set 9000 –ide2 local-zfs:cloudinit
34 sudo qm set 9000 –serial0 socket –vga serial0

This was on proxmox-ve 7.1-1, with qemu-server 7.1-4.

Thanks for your tutorial.

Until my update to the latest version, a template no longer works. No HDD is created. In the template there is only one unused disk but I think that was normal.
Can you confirm that it does not work with the latest version of Proxmox?

Clearly you are already very aware of the HashiCorp tools, so why not just use Packer instead of all this command-line stuff. You can install the qemu-guest-agent (and any other packages you want to bake in – keeping in mind the trade offs between bake vs. fry) in cloud-init. Automation is really the best approach to repeatable builds right ?, especially when the implementation is a ‘hands free’ standard IaC pattern. One simple ‘packer build host.json’ command, and shortly thereafter a freshly minted Proxmox template. Then by all means use Terraform and friends (Vault, scripting, whatever) for the final mile processing at launch time (fry) ?

You tutorial has been very useful! Thank you.
I wish Proxmox had exposed an API for “qm importdisk”, everything could have been done with Packer/Terraform. Because of that missing functionality one has to interact with native “qm” commands.

virt-sysprep -a focal-server-cloudimg-amd64.img
This will resolve duplicate DHCP IP addresses by resetting the machine-id along with other clean ups.

Hi Austin,

First, congrats for this post and the series.

I followed the steps creating two templates and correctly instantiated two VMS, in my case I used (Rocky and Ubuntu cloud images).

When I start the machines, I noticed that the VMs doesn’t boot at all, receiving a Probing EDD message on the boot of the Rocky machine, and a unsuccesful boot on the Ubunut machine.

I noticed that the Display is configured for the Serial Terminal, which I undestand that is for connecting in the future by ipmitool, but, did you installed any other agent or driver to support for example the Spice agent and the compatibility with this option on the Proxmox configuration?

Thanks for your dedication!

Hi there, I have not seen that error message before, for any VM. I didn’t install any other driver/agent. Everything just worked for me. Did you watch the YouTube video that goes with this tutorial? It shows everything from beginning to end.

Hi Austin,

I followed the steps posted on this blog entry. Let me follow the video to check that I followed fine all the steps, and in case that would not work, I will check using the same versions of Ubuntu as you. I was using a more updated release.


Hi Austin,

Solved, as my storage is different than yours there are some commands that are slightly different.

Now I’ve created Ubuntu 2204, 2004 and Rocky 8.5 without problems.

Thanks for your posts.

I’m seeing a error that sounds similar to yours. Would you be able to post your different commands?

Leave a Reply

Your email address will not be published.