Install KVM and Build a Custom Ubuntu 24.04 Cloud Image

This post documents the complete process of installing KVM on the management server and building a clean, optimized, minimal Ubuntu 24.04 VM template. This template will serve as the base image for all orchestrated VMs in the lab environment.

1. Install KVM

Install KVM, libvirt, and required virtualization tools:

apt -y install qemu-system-x86 libvirt-daemon-system libvirt-daemon virtinst \
               bridge-utils libguestfs-tools libosinfo-bin ovmf qemu-guest-agent \
               virt-manager

Ensure VMs auto-start when libvirtd restarts:

nano /etc/default/libvirt-guests

Uncomment and set:

ON_BOOT=start

2. Create a New Ubuntu 24.04 VM

Using virt-manager, create a new VM:

  • Name: ubuntu24
  • Disk size: 25 GB recommended
  • ISO: Ubuntu Server 24.04
  • Kernel: Select Install HWE Kernel

Once installation completes, reboot to begin cleanup and optimization.

3. Remove snapd Completely

Snap is not required for VM workloads; remove it:

snap list
snap remove lxd
snap remove core20
snap remove snapd
apt purge --remove snapd
rm -rf /root/snap/

4. Disable Swap

systemctl list-units | grep swap
systemctl stop swap.target
systemctl disable swap.target
systemctl mask swap.target
swapoff -a

Edit /etc/fstab and comment out any swap entries.

5. GRUB updates

Edit /etc/default/grub and update following lines

GRUB_CMDLINE_LINUX_DEFAULT=”net.ifnames=0 biosdevname=0 cpufreq.default_governor=performance”
GRUB_TERMINAL=”console serial”
GRUB_SERIAL_COMMAND=”serial –speed=115200 –unit=0 –word=8 –parity=no –stop=1″

net.ifnames=0 & biosdevname=0: Disables predictable naming to force the classic eth0 convention. This ensures that manually injected Netplan configurations “hit” the correct interface without needing to probe for hardware-specific names (like enp0s3) during orchestration.

cpufreq.default_governor=performance: Eliminates CPU frequency scaling latency. The VM operates at maximum clock speed immediately upon boot, which is critical for consistent AI workload performance.

GRUB_CMDLINE_LINUX="": Kept empty to ensure the kernel parameters remain modular and easily overridable via the default string.

GRUB_TERMINAL="console serial": Dual-routes the bootloader output to both virtual VGA and the serial port. This allows orchestration logs to be captured via virsh console even if the VM is headless.

GRUB_SERIAL_COMMAND: Standardizes the serial interface at 115200 baud, ensuring that host-side monitoring scripts can reliably parse boot and kernel messages from the start.

Apply:

update-grub
reboot

After reboot:

rm -f /etc/cloud/cloud.cfg.d/90-installer-network.cfg

Create a minimal DHCP config for eth0 only (placeholder file for orchestration later).

Disable cloud-init networking:

echo "network: {config: disabled}" > /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg

6. Configure File Descriptor and Process Limits

echo "* hard nofile 65536" >> /etc/security/limits.conf
echo "* soft nofile 65536" >> /etc/security/limits.conf
echo "* hard nproc 65536" >> /etc/security/limits.conf
echo "* soft nproc 65536" >> /etc/security/limits.conf

7. Limit Journal Size

sed -i "s/#SystemMaxFileSize.*/SystemMaxFileSize=512M/g" /etc/systemd/journald.conf

8. Fix resolv.conf to Use systemd-resolved

Edit the contents of /etc/systemd/resolved.conf - Update DNS details

DNS=<Your DNS Server IP> 
FallbackDNS=8.8.8.8
Domains=<your domain>
#DNSSEC=no
#DNSOverTLS=no
#MulticastDNS=no
#LLMNR=no
#Cache=no-negative
#CacheFromLocalhost=no
DNSStubListener=no
#DNSStubListenerExtra=
#ReadEtcHosts=yes
#ResolveUnicastSingleLabel=no



ln -fs /run/systemd/resolve/resolv.conf /etc/resolv.conf

systemctl restart systemd-resolved

9. Install Standard Required Packages

apt -y install net-tools rsyslog bc fio iperf3 gnupg2 software-properties-common lvm2 nfs-common jq

10. Clean Up SSH Login Messages

Edit PAM config:

nano /etc/pam.d/ssh

Comment out:

#session    optional     pam_motd.so  motd=/run/motd.dynamic
#session    optional     pam_motd.so noupdate
#session    optional     pam_mail.so standard noenv

Disable MOTD news:

echo "ENABLED=0" >> /etc/default/motd-news

11. Disable Unwanted Timer Services

List timers:

systemctl list-units | grep timer

Disable:

systemctl stop apt-daily-upgrade.timer apt-daily.timer fwupd-refresh.timer \
               motd-news.timer update-notifier-download.timer update-notifier-motd.timer

systemctl disable apt-daily-upgrade.timer apt-daily.timer fwupd-refresh.timer \
                  motd-news.timer update-notifier-download.timer update-notifier-motd.timer

systemctl mask apt-daily-upgrade.timer apt-daily.timer fwupd-refresh.timer \
               motd-news.timer update-notifier-download.timer update-notifier-motd.timer

12. Disable Unattended Upgrades

systemctl stop unattended-upgrades.service
systemctl disable unattended-upgrades.service
systemctl mask unattended-upgrades.service

13. Disable Ubuntu Advantage Services

systemctl stop ubuntu-advantage-tools
systemctl disable ubuntu-advantage-tools
systemctl mask ubuntu-advantage-tools

14. Disable AppArmor and UFW

These security layers are unnecessary within a trusted, firewalled lab network.

systemctl stop apparmor ufw
systemctl disable apparmor ufw
systemctl mask apparmor ufw

15. Prepare System for Export

Remove unused packages:

apt autoremove --purge -y

Generate SSH key:

ssh-keygen

Set root password:

passwd

Enable root login:

PermitRootLogin yes
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys .ssh/authorized_keys2

Optimize filesystem and compact:

e4defrag /
fstrim -av
dd if=/dev/zero of=/zero.fill bs=1M status=progress
rm -f /zero.fill
fstrim -av

Final Cleanup and Shutdown

truncate -s 0 /etc/machine-id (Essential for DHCP—many servers use this ID rather than the MAC address to assign IPs).

SSH Host Keys: rm /etc/ssh/ssh_host_* (Ensures each VM generates its own unique fingerprint on first boot).

cat <<EOF > /etc/rc.local
#!/bin/bash
if [ ! -f /etc/ssh/ssh_host_rsa_key ]; then
    ssh-keygen -A
    systemctl restart ssh
fi
EOF
chmod +x /etc/rc.local


history -c
shutdown -h now

16. Export the qcow2 Base Image

Use virt-sparsify to compress and generate the reusable base image:

virt-sparsify --compress /var/lib/libvirt/images/ubuntu24.qcow2  /root/kvm-local/ubuntu24/base.qcow2

This final QCOW2 file becomes the VM template used by the automation/orchestration system.