A pre-configured operating system can be saved as a template for faster virtual machines creation as day 2 operation. In this tutorial, we will create a Rocky Linux 9 and Rocky Linux 8 template from cloud images of the said OS. This applies only to the server edition releases of Rocky Linux, as the desktop variant will require installation from a DVD ISO image, which is beyond the scope of this article.
Follow the following steps to create a customized OS template from Rocky Linux cloud images.
1 – Download Rocky Linux Cloud Images
Begin by downloading the Rocky Linux QCOW2 cloud images:
Use wget to directly download the latest generic Qcow2 images from source:
- Rocky Linux 10:
wget https://dl.rockylinux.org/pub/rocky/10/images/x86_64/Rocky-10-GenericCloud-Base.latest.x86_64.qcow2
# For LVM
wget https://dl.rockylinux.org/pub/rocky/10/images/x86_64/Rocky-10-GenericCloud-LVM.latest.x86_64.qcow2
- Rocky Linux 9:
wget https://dl.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud.latest.x86_64.qcow2
- Rocky Linux 8:
wget https://dl.rockylinux.org/pub/rocky/8/images/x86_64/Rocky-8-GenericCloud.latest.x86_64.qcow2
This should done on Proxmox server, or a KVM host where libguestfs-tools can be installed.
2 – Define template variables
Login to your Proxmox host, and list available configured storage pools:
# 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%
In this guide, we will be using local storage pool. Next list configured networks:
# brctl show
bridge name bridge id STP enabled interfaces
vmbr0 8000.5ced8cedb886 no eno1
vmbr1 8000.5ced8cedb889 no eno4
- Rocky Linux 9:
# Image path
IMAGE=./Rocky-9-GenericCloud.latest.x86_64.qcow2
# VM ID / Template ID
TID=800
# Template name
TNAME=Rocky-9-Template
# Disk size
SIZE=20G
# Network bridge
BRIDGE=vmbr0
# RAM
RAM=2048
# CPU cores
CORES=1
# Storage pool
STORAGE=local
# Cloud Init user
CUSER=rocky
# Cloud Init user password
CPASS="Str0ngUserPassW0rd"
- Rocky Linux 8:
# Image path
IMAGE=./Rocky-8-GenericCloud.latest.x86_64.qcow2
# VM ID / Template ID
TID=801
# Template name
TNAME=Rocky-8-Template
# Disk size
SIZE=20G
# Network bridge
BRIDGE=vmbr0
# RAM
RAM=2048
# CPU cores
CORES=1
# Storage pool
STORAGE=local
# Cloud Init user
CUSER=rocky
# Cloud Init user password
CPASS="Str0ngUserPassW0rd"
NATed network creation guide: Creating Private Network Bridge on Proxmox VE with NAT
3 – Image customization using virt-customize
We will need the virt-customize command which is provided by libguestfs-tools package. Let’s install it on Proxmox server or on a KVM node if you have one.
sudo apt update && sudo apt install libguestfs-tools
If installed properly, you should be able to check version.
virt-customize --version
We’ll use virt-customize to install basic packages, such as Vim, and qemu-guest-agent into the base cloud image.
# virt-customize -a $IMAGE --install vim,unzip,bash-completion,wget,curl,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 unzip bash-completion wget curl qemu-guest-agent
[ 163.2] Finishing off
Use the same command to enable qemu-guest-agent service to start at 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
Set the default timezone for the OS. Replace UTC with your timezone, e.g `America/New_York”
# virt-customize -a $IMAGE --timezone "UTC"
[ 0.0] Examining the guest ...
[ 7.5] Setting a random seed
[ 7.6] Setting the timezone: UTC
[ 8.0] Finishing off
For better security, we recommend disabling SSH password authentication. To enable it, run the commands below:
# 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
It is recommended to disable root user ssh login and use standard user set for cloud init.
# 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, as it is enforced by default.
# 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 the commands below.
# 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 Rocky Linux OS template on Proxmox
Expand the image disk to the size defined in the SIZE variable.
# qemu-img resize $IMAGE $SIZE
Image resized.
Create a VM with set ID, RAM, CPU and Network bridge.
qm create $TID \
--name $TNAME \
--memory $RAM \
--cores $CORES \
--net0 virtio,bridge=$BRIDGE \
--scsihw virtio-scsi-pci \
--cpu host
We created a VM without disk attached. 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 801 ...
Formatting '/var/lib/vz/images/801/vm-801-disk-0.raw', fmt=raw size=107374182400 preallocation=off
...
transferred 89.6 GiB of 100.0 GiB (89.59%)
transferred 90.6 GiB of 100.0 GiB (90.59%)
transferred 91.6 GiB of 100.0 GiB (91.62%)
transferred 92.6 GiB of 100.0 GiB (92.62%)
transferred 93.6 GiB of 100.0 GiB (93.62%)
transferred 94.6 GiB of 100.0 GiB (94.62%)
transferred 95.6 GiB of 100.0 GiB (95.65%)
transferred 96.6 GiB of 100.0 GiB (96.65%)
transferred 97.6 GiB of 100.0 GiB (97.65%)
transferred 98.6 GiB of 100.0 GiB (98.65%)
transferred 99.6 GiB of 100.0 GiB (99.65%)
transferred 100.0 GiB of 100.0 GiB (100.00%)
transferred 100.0 GiB of 100.0 GiB (100.00%)
unused0: successfully imported disk 'local:801/vm-801-disk-0.raw'
After disk is imported, attach it to the VM created.
- Using local dir storage volume
# qm set $TID --scsihw virtio-scsi-pci --virtio0 $STORAGE:$TID/vm-$TID-disk-0.raw
update VM 801: -scsihw virtio-scsi-pci -virtio0 local:801/vm-801-disk-0.raw
- Using LVM or ZFS Pool:
# qm set $TID --scsihw virtio-scsi-pci --virtio0 $STORAGE:vm-$TID-disk-0
update VM 800: -scsihw virtio-scsi-pci -virtio0 local-zfs:vm-800-disk-0
Enable QEMU guest agent for the instance.
# qm set $TID --agent 1
update VM 801: -agent 1
Change boot order to start with VirtIO block device.
# qm set $TID --boot c --bootdisk virtio0
update VM 801: -boot c -bootdisk virtio0
Attach cloud init image:
# qm set $TID --ide2 $STORAGE:cloudinit
update VM 801: -ide2 local:cloudinit
Formatting '/var/lib/vz/images/801/vm-801-cloudinit.qcow2', fmt=qcow2 cluster_size=65536 extended_l2=off preallocation=metadata compression_type=zlib size=4194304 lazy_refcounts=off refcount_bits=16
ide2: successfully created disk 'local:801/vm-801-cloudinit.qcow2,media=cdrom'
Use of uninitialized value in split at /usr/share/perl5/PVE/QemuServer/Cloudinit.pm line 106.
generating cloud-init ISO
Create a custom SSH public key file and paste your SSH key into it. Alternatively, you can generate an SSH key pair using ssh-keygen:
vim sshpub.key
Inject SSH key for the specified default user. Path to SSH public key can be changed accordinly.
# qm set $TID --sshkey ./sshpub.key
update VM 700: -sshkeys ssh-rsaxxxyyyzzz
Set default IP assignment to DHCP, and update username and password.
# qm set $TID --ciuser=$CUSER --cipassword="$CPASS" --ipconfig0 ip=dhcp
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 a template.
qm template $TID
5 – Creating a Virtual Machine from the Template
List available templates
# qm list
VMID NAME STATUS MEM(MB) BOOTDISK(GB) PID
800 Rocky-9-Template stopped 8192 10.00 0
801 Rocky-8-Template stopped 2048 100.00 0
Define all the variables required to create a VM from template.
TID=800 # Template ID
VMID=$(pvesh get /cluster/nextid) #ID of the VM to be created. This is auto-assigned by Proxmox VE
VMNAME=AppServer # Name of the VM
RAM=4096 # VM RAM size in MB
CORES=2 # CPU cores for the VM
It is also possible to define custom network configurations such as static IP address, gateway and netmask.
BRIDGE=vmbr0 # Name of the bridge attached to the VM
IP=192.168.10.11/24 # IP address (If using static IP addressing)
GW=192.168.10.1 # Default gateway IP Address
DNS=8.8.8.8 # Default DNS server
SDOMAINS="example.com" # Search domains, separate with , for multiple.
There are two standard options for cloning a template into virtual machine:
- Linked Clone – A VM created from this type uses less disk space but depends on the base VM template and cannot run without access to it.
- Full Clone – A VM created from a full clone is a complete copy and operates independently from the original VM template, but it requires the same amount of disk space as the original.
An example on full cloning of the template:
qm clone $TID $VMID --full --name $VMNAME --format qcow2
Linked clone example:
qm clone $TID $VMID --name $VMNAME
Modify CPU and RAM allocations:
# qm set $VMID --vcpus $CORES --cores $CORES
update VM 100: -vcpus 1
# qm set $VMID --memory $RAM
update VM 100: -memory 4096
Finally, update changes for the IP, DNS and Search domain(s) if you need static IP address:
# qm set $VMID --ipconfig0 ip=$IP,gw=$GW
update VM 100: -ipconfig0 ip=192.168.10.11/29,gw=192.168.10.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
You can also provide a custom ssh public key to override the default.
qm set $VMID --sshkey $SSHKEY
Make sure the virtual machine is set to start on boot
qm set $VMID --onboot 1
The virtual machine can be started from CLI or web console
qm start $VMID
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!