In Proxmox VE, a VM template is an image of the operating system that is pre-configured for use when spinning new Virtual Machines. It contains complete installation of the OS alongside other additional software packages and settings required to run the operating system. Proxmox VE supports and allows users to create custom OS templates for most Linux distributions and Windows Desktop / Server editions.
The main objective of using OS templates is the simplification of creating new VMs. Templates provides a standardized and quicker way of OS deployment without the use of ISO images which can be long and tedious. Creating a new VM with the template is as easy as selecting OS template, customizing hardware settings, resources allocated, and starting the Virtual Machine within seconds.
In our previous article we discussed on the creation of Debian and Ubuntu templates. The article is available in the following link:
Create Rocky / AlmaLinux / CentOS OS Templates on Proxmox VE
By the end of this article you will have created Rocky / AlmaLinux or CentOS OS template with tailored configurations that includes your app specific dependencies. We will work on the following Operating Systems:
- Rocky Linux 9 / Rocky Linux 8
- AlmaLinux 9 / AlmaLinux 8
- CentOS Stream 9 / CentOS Stream 8
Let’s dive in!
1. Download Cloud base image(s)
We’ll need base cloud image provided by most Linux distributions. The following variables are used:
- imageURL: Link to the cloud image
- IMAGE: The name of the cloud image downloaded as stored locally in the system
- TID: The ID of the template to be created.
- TNAME: The name assigned to the template being created.
- CUSER: We’ll use Cloud-init to customize settings such as OS user, password and network configurations. This the default OS name to be used.
Here are the examples.
Rocky Linux 8 Cloud image:
imageURL=https://dl.rockylinux.org/pub/rocky/8/images/x86_64/Rocky-8-GenericCloud.latest.x86_64.qcow2
wget $imageURL
IMAGE=Rocky-8-GenericCloud.latest.x86_64.qcow2
TID=700
TNAME=Rocky-8-Template
CUSER=rocky
Rocky Linux 9 Cloud Image:
imageURL=https://dl.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud.latest.x86_64.qcow2
wget $imageURL
IMAGE=Rocky-9-GenericCloud.latest.x86_64.qcow2
TID=701
TNAME=Rocky-9-Template
CUSER=rocky
AlmaLinux 8 Cloud Image:
imageURL=https://repo.almalinux.org/almalinux/8/cloud/x86_64/images/AlmaLinux-8-GenericCloud-latest.x86_64.qcow2
wget $imageURL
IMAGE=AlmaLinux-8-GenericCloud-latest.x86_64.qcow2
TID=702
TNAME=AlmaLinux-8-Template
CUSER=alma
AlmaLinux 9 Cloud Image:
imageURL=https://repo.almalinux.org/almalinux/9/cloud/x86_64/images/AlmaLinux-9-GenericCloud-latest.x86_64.qcow2
wget $imageURL
IMAGE=AlmaLinux-9-GenericCloud-latest.x86_64.qcow2
TID=703
TNAME=AlmaLinux-9-Template
CUSER=alma
CentOS 8 Stream Cloud Image
imageURL=https://cloud.centos.org/centos/8-stream/x86_64/images/CentOS-Stream-GenericCloud-8-latest.x86_64.qcow2
wget $imageURL
IMAGE=CentOS-Stream-GenericCloud-8-latest.x86_64.qcow2
TID=704
TNAME=CentOS-8-Stream-Template
CUSER=centos
CentOS 9 Stream Cloud Image
imageURL=https://cloud.centos.org/centos/9-stream/x86_64/images/CentOS-Stream-GenericCloud-9-latest.x86_64.qcow2
wget $imageURL
IMAGE=CentOS-Stream-GenericCloud-9-latest.x86_64.qcow2
TID=705
TNAME=CentOS-9-Stream-Template
CUSER=centos
2. Define template variables
Here we define the following variables required to create the template:
SIZE=50G #Default root disk size
BRIDGE=vmbr1 #Default network bridge name
RAM=2048 #Default VM Ram size
CORES=1 #Default CPU cores
STORAGE=local-zfs #Storage pool name to use, check with pvesm status
CPASS="Str0nGUserPasswr=ord" #CloudInit User password to be injected
To see available network bridges, use:
# brctl show
bridge name bridge id STP enabled interfaces
vmbr0 8000.5ced8cedb886 no eno1
vmbr1 8000.5ced8cedb889 no eno4
If you need guidance on creating a Linux bridge on PVE check out: Create Private Network Bridge on Proxmox VE with NAT
Listing available Storage Pools can be done with the following commands:
# pvesm status
Name Type Status Total Used Available %
local dir active 1884995968 10524288 1874471680 0.56%
local-zfs zfspool active 1875328912 857128 1874471784 0.05%
3. Customize image using virt-customize
The virt-customize
command is provided by libguestfs-tools package. Let’s install it.
sudo apt update && sudo apt install libguestfs-tools
After the installation we can confirm if virt-customize
is working.
# virt-customize --version
virt-customize 1.51.6
Use virt-customize to install basic key packages in the base cloud image.
# virt-customize -a $IMAGE --install vim,bash-completion,wget,curl,unzip,qemu-guest-agent
[ 0.0] Examining the guest ...
[ 8.2] Setting a random seed
[ 8.3] Setting the machine ID in /etc/machine-id
[ 8.3] Installing packages: vim bash-completion wget curl unzip qemu-guest-agent
[ 163.2] Finishing off
Next we enable qemu-guest-agent service to start on system boot.
# virt-customize -a $IMAGE --run-command 'systemctl enable qemu-guest-agent'
[ 0.0] Examining the guest ...
[ 7.6] Setting a random seed
[ 7.6] Running: systemctl enable qemu-guest-agent
[ 8.2] Finishing off
You can also set the default timezone
# virt-customize -a $IMAGE --timezone "Africa/Nairobi"
[ 0.0] Examining the guest ...
[ 7.5] Setting a random seed
[ 7.6] Setting the timezone: Africa/Nairobi
[ 8.0] Finishing off
It’s possible to enable SSH Password Authentication. This is not recommended though. It’s necessary if you’re not using SSH keys.
# virt-customize -a $IMAGE --run-command 'sed -i "s/.*PasswordAuthentication.*/PasswordAuthentication yes/g" /etc/ssh/sshd_config'
[ 0.0] Examining the guest ...
[ 7.4] Setting a random seed
[ 7.5] Running: sed -i "s/.*PasswordAuthentication.*/PasswordAuthentication yes/g" /etc/ssh/sshd_config
[ 8.1] Finishing off
Disable SSL login for root user.
# virt-customize -a $IMAGE --run-command 'sed -i "s/.*PermitRootLogin.*/PermitRootLogin no/g" /etc/ssh/sshd_config'
[ 0.0] Examining the guest ...
[ 7.2] Setting a random seed
[ 7.3] Running: sed -i "s/.*PermitRootLogin.*/PermitRootLogin no/g" /etc/ssh/sshd_config
[ 7.7] Finishing off
Relabel SELinux
# virt-customize -a $IMAGE --selinux-relabel
[ 0.0] Examining the guest ...
[ 7.4] Setting a random seed
[ 7.4] SELinux relabelling
[ 7.9] Finishing off
To disable SELinux completely, run:
# virt-customize -a $IMAGE --run-command ' sed -i "s/^SELINUX=.*/SELINUX=disabled/g" /etc/selinux/config'
[ 0.0] Examining the guest ...
[ 4.7] Setting a random seed
guest
[ 4.8] Running: sed -i "s/^SELINUX=.*/SELINUX=disabled/g" /etc/selinux/config
[ 4.9] Finishing off
4. Create OS template on Proxmox VE
Resize the base image to your desired OS /
disk size as defined in the SIZE variable earlier on.
# qemu-img resize $IMAGE $SIZE
Image resized.
Now create the OS template skeleton with memory, CPU, and network bridge.
qm create $TID \
--memory $RAM \
--cores $CORES \
--net0 virtio,bridge=$BRIDGE \
--scsihw virtio-scsi-pci \
--cpu host
Import the base image we customized into the actual VM storage disk.
# qm importdisk $TID $IMAGE $STORAGE
importing disk 'Rocky-8-GenericCloud.latest.x86_64.qcow2' to VM 700 ...
transferred 0.0 B of 50.0 GiB (0.00%)
transferred 542.7 MiB of 50.0 GiB (1.06%)
transferred 1.0 GiB of 50.0 GiB (2.07%)
transferred 1.5 GiB of 50.0 GiB (3.09%)
transferred 2.0 GiB of 50.0 GiB (4.09%)
transferred 2.5 GiB of 50.0 GiB (5.10%)
transferred 3.1 GiB of 50.0 GiB (6.12%)
transferred 3.6 GiB of 50.0 GiB (7.12%)
transferred 4.1 GiB of 50.0 GiB (8.16%)
transferred 4.6 GiB of 50.0 GiB (9.19%)
...
transferred 49.4 GiB of 50.0 GiB (98.80%)
transferred 49.9 GiB of 50.0 GiB (99.80%)
transferred 50.0 GiB of 50.0 GiB (100.00%)
transferred 50.0 GiB of 50.0 GiB (100.00%)
Successfully imported disk as 'unused0:local-zfs:vm-700-disk-0'
Attach the disk into the VM. After import it is not attached.
qm set $TID --scsihw virtio-scsi-pci --virtio0 $STORAGE:$TID/vm-$TID-disk-0.raw
If using LVM or ZFS Pool then use:
# qm set $TID --scsihw virtio-scsi-pci --virtio0 $STORAGE:vm-$TID-disk-0
update VM 700: -scsihw virtio-scsi-pci -virtio0 local-zfs:vm-700-disk-0
Enable serial console for the virtual machine.
# qm set $TID --serial0 socket --vga serial0
update VM 700: -serial0 socket -vga serial0
Enable QEMU guest agent.
# qm set $TID --agent 1
update VM 700: -agent 1
Change boot order to start with SCSI or VirtIO block device.
# qm set $TID --boot c --bootdisk virtio0
update VM 700: -boot c -bootdisk virtio0
5. Attach cloud init image and create template
Next we attach cloud init image by running the following commands.
# qm set $TID --ide2 $STORAGE:cloudinit
update VM 700: -ide2 local-zfs:cloudinit
ide2: successfully created disk 'local-zfs:vm-700-cloudinit,media=cdrom'
generating cloud-init ISO
Inject SSH key for the specified default user. Path to SSH public key can be changed accordinly.
# qm set $TID --sshkey ~/.ssh/id_rsa.pub
update VM 700: -sshkeys ssh-rsaxxxyyyzzz
Also set default networking IP assignment to DHCP, and set Cloud Init user password.
# qm set $TID --ipconfig0 ip=dhcp --cipassword="$CPASS" --ciuser=$CUSER
update VM 700: -cipassword <hidden> -ciuser rocky -ipconfig0 ip=dhcp
Assign a name to the VM using the following commands.
# qm set $TID --name $TNAME
update VM 700: -name Rocky-8-Template
Finally convert the Virtual Machine into template.
qm template $TID
6. Creating a Virtual Machine from the Template
List available templates
# qm list
VMID NAME STATUS MEM(MB) BOOTDISK(GB) PID
700 Rocky-8-Template stopped 2048 50.00 0
780 Debian-12-Template stopped 2048 50.00 0
Define variable for the VM creation
TID=700 # ID of the template to be used.
VMID=$(pvesh get /cluster/nextid) #ID of the VM to be created. This is auto-assigned by Proxmox VE
VMNAME=DirectAdmin # Name of the VM
RAM=4096 # VM RAM size in MB
CORES=2 # CPU cores for the VM
Next define network configuration parameters
BRIDGE=vmbr1 # Name of the bridge attached to the VM
IP=192.168.20.11/24 # IP address (If using static IP addressing)
GW=192.168.20.1 # Default gateway IP Address
DNS=8.8.8.8 # Default DNS server
SDOMAINS="example.com" # Search domains, separate with , for multiple.
We create a VM instance by cloning the template. There are two standard options for this.
- Linked Clone – VM created from this requires less disk space but cannot run without access to the base VM Template
- Full Clone – A VM created from full clone is a complete copy and is fully independent of the original VM Template, but requires the same disk space as the original.
Full clone example.
# qm clone $TID $VMID --full --name $VMNAME --format qcow2
...
transferred 44.2 GiB of 50.0 GiB (88.34%)
transferred 44.7 GiB of 50.0 GiB (89.35%)
transferred 45.2 GiB of 50.0 GiB (90.35%)
transferred 45.7 GiB of 50.0 GiB (91.36%)
transferred 46.2 GiB of 50.0 GiB (92.36%)
transferred 46.7 GiB of 50.0 GiB (93.36%)
transferred 47.2 GiB of 50.0 GiB (94.37%)
transferred 47.7 GiB of 50.0 GiB (95.37%)
transferred 48.2 GiB of 50.0 GiB (96.37%)
transferred 48.7 GiB of 50.0 GiB (97.38%)
transferred 49.2 GiB of 50.0 GiB (98.38%)
transferred 49.7 GiB of 50.0 GiB (99.39%)
transferred 50.0 GiB of 50.0 GiB (100.00%)
transferred 50.0 GiB of 50.0 GiB (100.00%)
Linked clone example
qm clone $TID $VMID --name $VMNAME
Adjust CPU cores and memory
# qm set $VMID --vcpus $CORES --cores $CORES
update VM 100: -vcpus 1
# qm set $VMID --memory $RAM
update VM 100: -memory 4096
Update IP, DNS and Search domain(s)
# qm set $VMID --ipconfig0 ip=$IP,gw=$GW
update VM 100: -ipconfig0 ip=192.168.20.11/29,gw=192.168.20.1
# qm set $VMID --searchdomain $SDOMAINS
update VM 100: -searchdomain example.net
# qm set $VMID --nameserver $DNS
update VM 100: -nameserver 8.8.8.8
Configure SSH public key used for authentication
qm set $VMID --sshkey $SSHKEY
Set the VM to set on boot
qm set $VMID --onboot 1
Start the VM instance
qm start $VMID
Conclusion
In this article we’ve been able create OS template of Rocky / AlmaLinux / CentOS 8|9 in Proxmox Virtual Environment. Once we’ve created the Virtual Machine by cloning the template we can start as demonstrated from CLI or using Proxmox web console. To get access to the instance use remote console such as SSH, VNC, or web console and administer your OS. We hope this article was informative.
Hi there, your setup for templates looks a little bit complicated. I usually create installation of desired OS, make all basic setttings, install basic set of SW and convert it into a template.
I understand your point of view. This guide was geared more into semi-automated templates creation. It was actually a documentation from the work I did for a client.
Hey,
Just wanted to let you know that this is a great work, virt-customize saves a bunch of times in doing modifications to the image. Also, I had to change the CPU configuration to the clone and add “host” as CPU type to make it work, it was stuck in grub all the time. Another point is that I had to change the serial to the default one (probably this is just from my Proxmox configuration.
Thanks again for this tutorial!
Great!