Introduction
In this guide we will go over creating a Proxmox KVM Template from a Cloud Image. This same process will work for any Cloud-Init Openstack based image type you can find online.
Having done a number of these for our Proxmox based VPS service I wanted to post up a guide to help anyone else looking to do the same thing.
My workflow for customizing one of those for use with Proxmox with cloud-init deployment from WHMCS and root login is below. Once you setup one template you can rapidly reinstall new containers and test stuff.
Setup Environment
If not installed already installed you will need
apt-get install libguestfs-tools
To edit the image before importing. We will use
For the sake of sanity, I highly advise setting your default editor to “nano” because using the default
You can see your default editor with the the below command.
xxxxxxxxxx
printenv | grep EDITOR
To set nano as default if it’s not already. use the below command. Please Note: nano should be installed(should be by default on Ubuntu/Debian systems)
xxxxxxxxxx
export EDITOR=nano
It should now look like the below.
xxxxxxxxxx
root@coby:~# printenv | grep EDITOR
EDITOR=/bin/nano
root@coby:~#
Grab the Cloud Image
Grab the cloud image base link for desired OS: Openstack has a nice page on this: https://docs.openstack.org/image-guide/obtain-images.html
These are some direct links:
https://cloud.centos.org/centos/7/images/
https://cloud-images.ubuntu.com/
https://repo.almalinux.org/almalinux/8/cloud/x86_64/images/
Ssh into your Proxmox server and fetch the image directly to the server. This saves you having to upload it to the server later.
xxxxxxxxxx
wget https://cloud.centos.org/centos/7/images/CentOS-7-x86_64-GenericCloud-1907.qcow2
Customize Image
We can now use
Here is the format for the command to edit by hand.
xxxxxxxxxx
virt-edit -a Cloudimagefilename /path/to/file/in/disk/image/cloud.cfg
So for our example of “CentOS-7-x86_64-GenericCloud-1907.qcow2” to open up the “/etc/cloud/cloud.
xxxxxxxxxx
virt-edit -a CentOS-7-x86_64-GenericCloud-1907.qcow2 /etc/cloud/cloud.cfg
Once opened it will look something like this.

The first thing you want to do is carefully edit the disable_root: 1 to disable_root: 0 This will set cloud-init to not disable this in the /etc/ssh/sshd_config on first boot and allow root based logins. It should look like the below.

Please note some OS cloud images use “true” or “false” where 1=true and 0=false. If they are using true and false vs 1 and 0 please use that formatting.

Once fixed it will look like this.

Another way to do the above is automatically via sed like manner non-interactively as outlined in the below link.
https://libguestfs.org/virt-edit.1.html#non-interactive-editing
To enable root and password logins for example the below commands could be used which accounts for all variations of True/true/1 being set which has it disabled.
xxxxxxxxxx
virt-edit -a ${image_name} /etc/cloud/cloud.cfg -e 's/disable_root: [Tt]rue/disable_root: False/'
virt-edit -a ${image_name} /etc/cloud/cloud.cfg -e 's/disable_root: 1/disable_root: 0/'
virt-edit -a ${image_name} /etc/cloud/cloud.cfg -e 's/lock_passwd: [Tt]rue/lock_passwd: False/'
virt-edit -a ${image_name} /etc/cloud/cloud.cfg -e 's/lock_passwd: 1/lock_passwd: 0/'
virt-edit -a ${image_name} /etc/cloud/cloud.cfg -e 's/ssh_pwauth: 0/ssh_pwauth: 1/';
packages:
- qemu-guest-agent
- nano
- wget
- curl
- net-tools
Should look like this. Sometimes I have to put these packages above the ssh_svcname: sshd block for some OS. So if it doesn’t work insert it above that block. See the cloud-init documentation for more details.

Now you can use ctrl+x and hit y to save and exit.
You can also splice in packages which is faster directly into the image which saves on the install first boot time by using virt-customize to inject/install packages into the image like in the below command.
xxxxxxxxxx
virt-customize --install cloud-init,atop,htop,nano,vim,qemu-guest-agent,curl,wget -a ${image_name}
Modify the sshd_config
Now we need to ensure the default /etc/ssh/sshd_config is setup to our liking and allows root login. We need to ensure the below values are set.
xxxxxxxxxx
PermitRootLogin yes
PasswordAuthentication yes
So now we reopen the image but this time were editing the file “/etc/ssh/sshd_config”
xxxxxxxxxx
virt-edit -a CentOS-7-x86_64-GenericCloud-1907.qcow2 /etc/ssh/sshd_config
Now we need to look for “PermitRootLogin” it may be commented out or set to “PermitRootLogin no” or “PermitRootLogin without-password”. make sure only one occurrence is enabled and it says “PermitRootLogin yes” like the below example.

To enable root logins via ssh and password authentication the below command will also handle that for you.
xxxxxxxxxx
virt-edit -a ${image_name} /etc/ssh/sshd_config -e 's/PasswordAuthentication no/PasswordAuthentication yes/';
virt-edit -a ${image_name} /etc/ssh/sshd_config -e 's/PermitRootLogin [Nn]o/PermitRootLogin yes/';
virt-edit -a ${image_name} /etc/ssh/sshd_config -e 's/#PermitRootLogin [Yy]es/PermitRootLogin yes/';
virt-edit -a ${image_name} /etc/ssh/sshd_config -e 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/'
Now we will scroll down and verify that “PasswordAuthentication yes” is set. Sometimes it is sometimes it isn’t so always check. In this template it was enabled. So we can save and close this now if no other customization’s are needed.

Editing existing images of imported images
Occasionally you will find you got everything except one thing working and you want to modify the image of existing VM. This is totally possible just ensure that the guest VM is completely powered off first.
To edit an already imported disk. This example is based on the
xxxxxxxxxx
virt-edit -a /var/lib/vz/images/104/vm-104-disk-0.raw /etc/cloud/cloud.cfg
Creating VM
Here we are going to tie it all together.
Create the VM: See the official link > https://pve.proxmox.com/wiki/Cloud-Init_Support
You may need to create the container if it does not exist already. The below is based on my storage for OS template VM using “local” storage yours may be “
Create a new VM
xxxxxxxxxx
qm create 9000 --memory 2048 --net0 virtio,bridge=vmbr0
Import the downloaded disk to local storage
xxxxxxxxxx
qm importdisk 9000 CentOS-7-x86_64-GenericCloud-1907.qcow2 local
Finally attach the new disk to the VM as scsi drive
xxxxxxxxxx
qm set 9000 --scsihw virtio-scsi-pci --scsi0 local:vm-9000-disk-1
Add Cloud-Init CDROM drive
The next step is to configure a CDROM drive which will be used to pass the Cloud-Init data to the VM.
xxxxxxxxxx
qm set 9000 --ide2 local:cloudinit
To be able to boot directly from the Cloud-Init image, set the bootdisk parameter to scsi0, and restrict BIOS to boot from disk only. This will speed up booting, because VM BIOS skips the testing for a bootable CDROM.
xxxxxxxxxx
qm set 9000 --boot c --bootdisk scsi0
Also configure a serial console and use it as a display. Many Cloud-Init images rely on this, as it is an requirement for OpenStack images.
xxxxxxxxxx
qm set 9000 --serial0 socket --vga serial0
In a last step, it is helpful to convert the VM into a template. From this template you can then quickly create linked clones. The deployment from VM templates is much faster than creating a full clone (copy).
xxxxxxxxxx
qm template 9000
Create a clone from the previous template.
xxxxxxxxxx
qm clone 9000 123 --name Centos-7-Minimal
Batch mode:
If you want to do this for a lot it can be tedious but you can do it pretty quickly with the below as a reference. The only thing to really change would be the cloud_image_url and vm_id variable and the rest should just go like clockwork.
xxxxxxxxxx
vm_id='2010'
cloud_img_url='https://cloud-images.ubuntu.com/daily/server/groovy/current/groovy-server-cloudimg-amd64-disk-kvm.img'
image_name=${cloud_img_url##*/} # focal-server-cloudimg-amd64.img
wget ${cloud_img_url}
# virt-edit -a ${image_name} /etc/cloud/cloud.cfg
virt-edit -a ${image_name} /etc/cloud/cloud.cfg -e 's/disable_root: [Tt]rue/disable_root: False/'
virt-edit -a ${image_name} /etc/cloud/cloud.cfg -e 's/disable_root: 1/disable_root: 0/'
virt-edit -a ${image_name} /etc/cloud/cloud.cfg -e 's/lock_passwd: [Tt]rue/lock_passwd: False/'
virt-edit -a ${image_name} /etc/cloud/cloud.cfg -e 's/lock_passwd: 1/lock_passwd: 0/'
virt-edit -a ${image_name} /etc/cloud/cloud.cfg -e 's/ssh_pwauth: 0/ssh_pwauth: 1/'
virt-edit -a ${image_name} /etc/ssh/sshd_config -e 's/PasswordAuthentication no/PasswordAuthentication yes/'
virt-edit -a ${image_name} /etc/ssh/sshd_config -e 's/PermitRootLogin [Nn]o/PermitRootLogin yes/'
virt-edit -a ${image_name} /etc/ssh/sshd_config -e 's/#PermitRootLogin [Yy]es/PermitRootLogin yes/'
virt-edit -a ${image_name} /etc/ssh/sshd_config -e 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/'
virt-edit -a ${image_name} /etc/ssh/sshd_config -e 's/[#M]axAuthTries 6/MaxAuthTries 20/'
virt-customize --install cloud-init,atop,htop,nano,vim,qemu-guest-agent,curl,wget -a ${image_name}
qm create ${vm_id} --memory 512 --net0 virtio,bridge=vmbr0,firewall=1
qm importdisk ${vm_id} ${image_name} local
qm set ${vm_id} --ide0 local:cloudinit
qm set ${vm_id} --boot c --bootdisk scsi0
qm set ${vm_id} --serial0 socket --vga serial0
qm template ${vm_id}
Create a text file named images.txt
xxxxxxxxxx
https://cloud-images.ubuntu.com/bionic/current/bionic-server-cloudimg-amd64.img
https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64-disk-kvm.img
https://cloud-images.ubuntu.com/minimal/releases/focal/release/ubuntu-20.04-minimal-cloudimg-amd64.img
https://cloud-images.ubuntu.com/daily/server/groovy/current/groovy-server-cloudimg-amd64-disk-kvm.img
https://cloud-images.ubuntu.com/hirsute/current/hirsute-server-cloudimg-amd64.img
https://cdimage.debian.org/cdimage/openstack/current-10/debian-10-openstack-amd64.qcow2
https://cdimage.debian.org/cdimage/openstack/current-9/debian-9-openstack-amd64.qcow2
http://cloud.centos.org/centos/7/images/CentOS-7-x86_64-GenericCloud.qcow2
https://cloud.centos.org/centos/8/x86_64/images/CentOS-8-GenericCloud-8.2.2004-20200611.2.x86_64.qcow2
https://cloud.centos.org/centos/8-stream/x86_64/images/CentOS-Stream-GenericCloud-8-20210210.0.x86_64.qcow2
https://download.fedoraproject.org/pub/fedora/linux/releases/32/Cloud/x86_64/images/Fedora-Cloud-Base-32-1.6.x86_64.qcow2
https://github.com/rancher/os/releases/download/v1.5.5/rancheros-openstack.img
https://mirror.pkgbuild.com/images/v20210315.17387/Arch-Linux-x86_64-cloudimg-20210315.17387.qcow2
https://repo.almalinux.org/almalinux/8/cloud/x86_64/images/AlmaLinux-8-GenericCloud-latest.x86_64.qcow2
https://mirror.pkgbuild.com/images/v20210315.17387/Arch-Linux-x86_64-cloudimg-20210315.17387.qcow2
Then you can run the below. I have done a loop through a bunch of urls like the below to prep them all in batch mode.
xxxxxxxxxx
for cloud_img_url in $(cat images.txt); do image_name=${cloud_img_url##*/};echo "Downloading ${image_name}";wget ${cloud_img_url};do echo "Editing ${image_name}";virt-edit -a ${image_name} /etc/cloud/cloud.cfg -e 's/disable_root: [Tt]rue/disable_root: False/'; virt-edit -a ${image_name} /etc/cloud/cloud.cfg -e 's/disable_root: 1/disable_root: 0/';virt-edit -a ${image_name} /etc/cloud/cloud.cfg -e 's/ssh_pwauth: 0/ssh_pwauth: 1/';virt-edit -a ${image_name} /etc/cloud/cloud.cfg -e 's/lock_passwd: [Tt]rue/lock_passwd: False/';virt-edit -a ${image_name} /etc/cloud/cloud.cfg -e 's/lock_passwd: 1/lock_passwd: 0/';virt-edit -a ${image_name} /etc/ssh/sshd_config -e 's/PasswordAuthentication no/PasswordAuthentication yes/';virt-edit -a ${image_name} /etc/ssh/sshd_config -e 's/PermitRootLogin [Nn]o/PermitRootLogin yes/';virt-edit -a ${image_name} /etc/ssh/sshd_config -e 's/#PermitRootLogin [Yy]es/PermitRootLogin yes/';virt-edit -a ${image_name} /etc/ssh/sshd_config -e 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/';virt-edit -a ${image_name} /etc/ssh/sshd_config -e 's/#MaxAuthTries 6/MaxAuthTries 20/';virt-customize --install cloud-init,atop,htop,nano,vim,qemu-guest-agent,curl,wget -a ${image_name};mv ${image_name} /var/lib/vz/template/kvm/${image_name};done
Conclusion
I hope you found this tutorial helpful in creating a Proxmox cloud-init KVM template. There is so much more you can do with cloud-init and libguestfs-tools like injecting apps and repos in the image before its even booted. We recommend making backups before major edits so you can revert back to the base image if there is an issue.
Resource and reference links.
https://docs.openstack.org/image-guide/modify-images.html
http://libguestfs.org/virt-edit.1.html
https://www.poftut.com/linux-kvm-qemu-virt-customize-tutorial/
xxxxxxxxxx
virt-customize --run-command 'yum -y install network-scripts traceroute atop sysstat' -a /var/lib/vz/images/104/vm-104-disk-0.raw