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.

4 COMMENTS

  1. 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.

  2. 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!

LEAVE A REPLY

Please enter your comment!
Please enter your name here