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

References:

LEAVE A REPLY

Please enter your comment!
Please enter your name here