Skip to content

Cloud Image Usage

Enterprise Linux distributions (AlmaLinux, Rocky Linux) provide pre-built cloud images distributed in qcow2 format. These images are small in size, boot quickly, come with Cloud-Init pre-installed, and are well-suited for rapid VM deployment on KVM/libvirt, OpenStack, or other virtualization platforms.

Terminal window
# AlmaLinux 9 GenericCloud image
wget https://repo.almalinux.org/almalinux/9/cloud/x86_64/images/AlmaLinux-9-GenericCloud-latest.x86_64.qcow2
# AlmaLinux 8 GenericCloud image
wget https://repo.almalinux.org/almalinux/8/cloud/x86_64/images/AlmaLinux-8-GenericCloud-latest.x86_64.qcow2
# Verify download integrity
wget https://repo.almalinux.org/almalinux/9/cloud/x86_64/images/CHECKSUM
sha256sum -c CHECKSUM --ignore-missing
Terminal window
# Rocky Linux 9 GenericCloud image
wget https://download.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud.latest.x86_64.qcow2
# Rocky Linux 8 GenericCloud image
wget https://download.rockylinux.org/pub/rocky/8/images/x86_64/Rocky-8-GenericCloud.latest.x86_64.qcow2
# Verify checksums
wget https://download.rockylinux.org/pub/rocky/9/images/x86_64/CHECKSUM
sha256sum -c CHECKSUM --ignore-missing
Terminal window
# Install the qemu-img tool
sudo dnf install -y qemu-img
# View image information
qemu-img info AlmaLinux-9-GenericCloud-latest.x86_64.qcow2

Example output:

image: AlmaLinux-9-GenericCloud-latest.x86_64.qcow2
file format: qcow2
virtual size: 10 GiB (10737418240 bytes)
disk size: 582 MiB
cluster_size: 65536
Format specific information:
compat: 1.1
compression type: zlib
lazy refcounts: false

virt-customize is a tool provided by libguestfs-tools that can modify disk images without booting a virtual machine.

Terminal window
sudo dnf install -y libguestfs-tools
Terminal window
# Make a copy of the image for customization (preserve the original)
cp AlmaLinux-9-GenericCloud-latest.x86_64.qcow2 almalinux9-custom.qcow2
# Set root password
sudo virt-customize -a almalinux9-custom.qcow2 \
--root-password password:YourSecurePassword123
# Install packages
sudo virt-customize -a almalinux9-custom.qcow2 \
--install vim-enhanced,htop,tmux,git,wget,curl
# Enable services
sudo virt-customize -a almalinux9-custom.qcow2 \
--run-command 'systemctl enable firewalld'
# Write files
sudo virt-customize -a almalinux9-custom.qcow2 \
--write '/etc/motd:Welcome to Custom AlmaLinux 9 Image'
# Upload a local file into the image
sudo virt-customize -a almalinux9-custom.qcow2 \
--upload /path/to/local/sshd_config:/etc/ssh/sshd_config
# Set hostname
sudo virt-customize -a almalinux9-custom.qcow2 \
--hostname custom-server
# Set timezone
sudo virt-customize -a almalinux9-custom.qcow2 \
--timezone Asia/Shanghai
# Run custom scripts
sudo virt-customize -a almalinux9-custom.qcow2 \
--run-command 'echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.d/99-custom.conf'
# Set SELinux relabel (recommended after modifying files)
sudo virt-customize -a almalinux9-custom.qcow2 \
--selinux-relabel
#!/bin/bash
# customize-image.sh - Batch customize cloud images
SOURCE_IMAGE="AlmaLinux-9-GenericCloud-latest.x86_64.qcow2"
CUSTOM_IMAGE="almalinux9-production.qcow2"
# Copy the original image
cp "${SOURCE_IMAGE}" "${CUSTOM_IMAGE}"
# Expand disk to 30GB
qemu-img resize "${CUSTOM_IMAGE}" 30G
# Perform customization
sudo virt-customize -a "${CUSTOM_IMAGE}" \
--root-password password:TempPassword123 \
--timezone Asia/Shanghai \
--install epel-release \
--install vim-enhanced,htop,tmux,git,wget,curl,net-tools,bind-utils \
--install firewalld,fail2ban,chrony \
--run-command 'systemctl enable firewalld' \
--run-command 'systemctl enable fail2ban' \
--run-command 'systemctl enable chronyd' \
--write '/etc/sysctl.d/99-custom.conf:net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
vm.swappiness = 10' \
--run-command 'echo "PermitRootLogin prohibit-password" >> /etc/ssh/sshd_config.d/99-custom.conf' \
--selinux-relabel
echo "Image customization complete: ${CUSTOM_IMAGE}"
qemu-img info "${CUSTOM_IMAGE}"

Before distributing a custom image, you should clean out instance-specific information:

Terminal window
# Clean the image (removes SSH keys, machine IDs, logs, etc.)
sudo virt-sysprep -a almalinux9-custom.qcow2
# Specify which cleanup operations to perform
sudo virt-sysprep -a almalinux9-custom.qcow2 \
--operations defaults,-ssh-userdir
# List all available cleanup operations
sudo virt-sysprep --list-operations

Creating Cloud Instances with virt-install

Section titled “Creating Cloud Instances with virt-install”
Terminal window
# Install KVM and management tools
sudo dnf install -y @virtualization
sudo systemctl enable --now libvirtd
# Verify virtualization support
virt-host-validate
Terminal window
# Create a working directory
mkdir -p /var/lib/libvirt/cloud-init
# Create meta-data
cat > /var/lib/libvirt/cloud-init/meta-data << 'EOF'
instance-id: vm-001
local-hostname: web-server-01
EOF
# Create user-data
cat > /var/lib/libvirt/cloud-init/user-data << 'EOF'
#cloud-config
hostname: web-server-01
fqdn: web-server-01.example.com
users:
- default
- name: admin
groups: wheel
sudo: ALL=(ALL) NOPASSWD:ALL
shell: /bin/bash
ssh_authorized_keys:
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... admin@workstation
package_update: true
packages:
- vim-enhanced
- htop
- nginx
runcmd:
- systemctl enable --now nginx
EOF
# Create network-config (optional, for static IP configuration)
cat > /var/lib/libvirt/cloud-init/network-config << 'EOF'
version: 2
ethernets:
eth0:
dhcp4: true
EOF
Terminal window
# Install genisoimage
sudo dnf install -y genisoimage
# Generate the seed ISO
genisoimage -output /var/lib/libvirt/cloud-init/seed.iso \
-volid cidata \
-joliet -rock \
/var/lib/libvirt/cloud-init/user-data \
/var/lib/libvirt/cloud-init/meta-data \
/var/lib/libvirt/cloud-init/network-config
Terminal window
# Prepare the disk image (copy cloud image as the VM disk)
VM_NAME="web-server-01"
IMAGE_DIR="/var/lib/libvirt/images"
sudo cp almalinux9-custom.qcow2 "${IMAGE_DIR}/${VM_NAME}.qcow2"
# Resize the disk
sudo qemu-img resize "${IMAGE_DIR}/${VM_NAME}.qcow2" 30G
# Create the VM with virt-install
sudo virt-install \
--name "${VM_NAME}" \
--memory 2048 \
--vcpus 2 \
--disk "${IMAGE_DIR}/${VM_NAME}.qcow2",format=qcow2 \
--disk /var/lib/libvirt/cloud-init/seed.iso,device=cdrom \
--os-variant almalinux9 \
--network bridge=virbr0,model=virtio \
--graphics none \
--console pty,target_type=serial \
--import \
--noautoconsole
# Check VM status
sudo virsh list --all
# View VM IP address
sudo virsh domifaddr "${VM_NAME}"
#!/bin/bash
# create-vms.sh - Batch create virtual machines
BASE_IMAGE="/var/lib/libvirt/images/almalinux9-custom.qcow2"
IMAGE_DIR="/var/lib/libvirt/images"
CI_DIR="/var/lib/libvirt/cloud-init"
SSH_KEY="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... admin@workstation"
VMS=("web-01" "web-02" "db-01")
MEMORY=2048
VCPUS=2
DISK_SIZE="30G"
for VM_NAME in "${VMS[@]}"; do
echo "=== Creating VM: ${VM_NAME} ==="
# Prepare the disk
sudo cp "${BASE_IMAGE}" "${IMAGE_DIR}/${VM_NAME}.qcow2"
sudo qemu-img resize "${IMAGE_DIR}/${VM_NAME}.qcow2" "${DISK_SIZE}"
# Generate cloud-init configuration
WORK_DIR="${CI_DIR}/${VM_NAME}"
mkdir -p "${WORK_DIR}"
cat > "${WORK_DIR}/meta-data" << METAEOF
instance-id: ${VM_NAME}
local-hostname: ${VM_NAME}
METAEOF
cat > "${WORK_DIR}/user-data" << USEREOF
#cloud-config
hostname: ${VM_NAME}
users:
- default
- name: admin
groups: wheel
sudo: ALL=(ALL) NOPASSWD:ALL
shell: /bin/bash
ssh_authorized_keys:
- ${SSH_KEY}
package_update: true
packages:
- vim-enhanced
- htop
USEREOF
# Generate ISO
genisoimage -output "${WORK_DIR}/seed.iso" \
-volid cidata -joliet -rock \
"${WORK_DIR}/user-data" \
"${WORK_DIR}/meta-data"
# Create the VM
sudo virt-install \
--name "${VM_NAME}" \
--memory ${MEMORY} \
--vcpus ${VCPUS} \
--disk "${IMAGE_DIR}/${VM_NAME}.qcow2",format=qcow2 \
--disk "${WORK_DIR}/seed.iso",device=cdrom \
--os-variant almalinux9 \
--network bridge=virbr0,model=virtio \
--graphics none \
--console pty,target_type=serial \
--import \
--noautoconsole
echo "${VM_NAME} created successfully"
done
echo ""
echo "=== All VM Status ==="
sudo virsh list --all
Terminal window
# View current size
qemu-img info /var/lib/libvirt/images/web-server-01.qcow2
# Expand to 50GB (VM must be shut down)
sudo virsh shutdown web-server-01
sudo qemu-img resize /var/lib/libvirt/images/web-server-01.qcow2 50G
sudo virsh start web-server-01

After the VM boots, you still need to expand the partition and filesystem:

Terminal window
# Execute the following after logging into the VM
# View disk layout
lsblk
# If using LVM (typical for cloud images)
sudo growpart /dev/vda 4 # Expand the partition (partition number may vary)
sudo pvresize /dev/vda4 # Expand the PV
sudo lvextend -l +100%FREE /dev/mapper/almalinux-root # Expand the LV
sudo xfs_growfs / # Expand the XFS filesystem
# Or: sudo resize2fs /dev/mapper/almalinux-root # For ext4
# If not using LVM (simple partitioning)
sudo growpart /dev/vda 1
sudo xfs_growfs /
Terminal window
# Shut down the VM
sudo virsh shutdown web-server-01
# Compress the qcow2 image (remove sparse space)
sudo qemu-img convert -O qcow2 -c \
/var/lib/libvirt/images/web-server-01.qcow2 \
/var/lib/libvirt/images/web-server-01-compressed.qcow2
# Use virt-sparsify for more thorough compression
sudo virt-sparsify --in-place /var/lib/libvirt/images/web-server-01.qcow2
Terminal window
# qcow2 to raw format
qemu-img convert -f qcow2 -O raw image.qcow2 image.raw
# raw to qcow2 format
qemu-img convert -f raw -O qcow2 image.raw image.qcow2
# qcow2 to vmdk (VMware format)
qemu-img convert -f qcow2 -O vmdk image.qcow2 image.vmdk
# qcow2 to vdi (VirtualBox format)
qemu-img convert -f qcow2 -O vdi image.qcow2 image.vdi
Terminal window
# List all virtual machines
sudo virsh list --all
# Start/Shutdown/Reboot
sudo virsh start web-server-01
sudo virsh shutdown web-server-01
sudo virsh reboot web-server-01
# Force shutdown
sudo virsh destroy web-server-01
# Connect to the VM console
sudo virsh console web-server-01
# Exit console: Ctrl+]
# View VM information
sudo virsh dominfo web-server-01
sudo virsh domifaddr web-server-01
# Create a snapshot
sudo virsh snapshot-create-as web-server-01 \
--name "before-upgrade" \
--description "Snapshot before upgrade"
# List snapshots
sudo virsh snapshot-list web-server-01
# Revert to a snapshot
sudo virsh snapshot-revert web-server-01 before-upgrade
# Delete a VM (keep disks)
sudo virsh undefine web-server-01
# Delete a VM (remove disks as well)
sudo virsh undefine web-server-01 --remove-all-storage

Using official cloud images with Cloud-Init and virt-install allows you to rapidly create standardized VM instances in a local KVM environment. This experience is very similar to creating ECS instances on a public cloud, making it an ideal solution for building private clouds and development/testing environments.