Create Rocky Linux 9/8 Template and VM on Proxmox

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

References:

Join our Linux and open source community. Subscribe to our newsletter for tips, tricks, and collaboration opportunities!

Recent Post

Unlock the Right Solutions with Confidence

At CloudSpinx, we don’t just offer services - we deliver clarity, direction, and results. Whether you're navigating cloud adoption, scaling infrastructure, or solving DevOps challenges, our seasoned experts help you make smart, strategic decisions with total confidence. Let us turn complexity into opportunity and bring your vision to life.

Leave a Comment

Your email address will not be published. Required fields are marked *

Related Post

In this tutorial we will cover step-by-step procedure of compiling Open vSwitch (OVS) from source code on Rocky Linux, AlmaLinux, […]

Open vSwitch (OVS) is an open source virtual switch widely adopted in network virtualization. OVS is popular in platforms like […]

Photoshop can be such a headache for both experts and beginners alike. Every time you open photoshop there’s always a […]

Let's Connect

Unleash the full potential of your business with CloudSpinx. Our expert solutions specialists are standing by to answer your questions and tailor a plan that perfectly aligns with your unique needs.
You will get a response from our solutions specialist within 12 hours
We understand emergencies can be stressful. For immediate assistance, chat with us now

Contact CloudSpinx today!

Download CloudSpinx Profile

Discover the full spectrum of our expertise and services by downloading our detailed Company Profile. Simply enter your first name, last name, and email address.