Bash scripts and templates to create, deploy, and remove cloud-init-enabled virtual machines
using virt-install. It has been tested on Ubuntu 26.04 LTS and works
on other recent Ubuntu/Debian systems with a proper libvirt setup.
sudo apt update
sudo apt install libvirt-daemon-system libvirt-clients qemu-kvm qemu-utils virtinst wgetFor non-root usage, add your user to the libvirt and kvm groups:
sudo usermod -aG libvirt,kvm $USERThese commands must be available on the host where you run the scripts (local system or the remote hypervisor when using LIBVIRT_DEFAULT_URI):
virshvirt-installwget
Example templates are provided in the templates directory:
cloud-config.yml-examplelaunch-vm.ini-example
To set up your configuration:
cp templates/cloud-config.yml-example templates/cloud-config.yml
cp templates/launch-vm.ini-example templates/launch-vm.iniEdit:
launch-vm.inito configure network, CPUs, RAM, storage pool, etc.cloud-config.ymlto define the default user, password, and SSH key.
Use the helper script to create a SHA-512 hash for the passwd: field in cloud-config.yml:
bin/gen-passwd-hash.shOr from stdin (raw hash only):
echo 'secret' | bin/gen-passwd-hash.sh --stdin --rawTo support additional distributions, create an INI file in templates/ (e.g., debian12.ini) with:
SOURCE=source-debian12.img
URL=https://cloud.debian.org/images/cloud/bookworm/daily/latest/debian-12-generic-amd64-daily.qcow2
OSVARIANT=debian12
VMPOOL=vm-pool
CONSOLE="pty,target_type=virtio"This script stores base images and cloned VMs in a libvirt storage pool (vm-pool by default). Create it if it doesn't exist:
virsh pool-define-as vm-pool dir - - - - "/var/lib/libvirt/images/vms"
virsh pool-build vm-pool
virsh pool-start vm-pool
virsh pool-autostart vm-pool./bin/launch-vm.sh -d ubuntu22.04 -n MyUbuntuVM -c 2 -m 2048 -s 32 -a 50 -N isolated-d ubuntu22.04→ Usestemplates/ubuntu22.04.ini-n MyUbuntuVM→ VM name-c 2→ CPUs-m 2048→ Memory (MB)-s 32→ Disk size (GB)-a 50→ Additional data disk size (GB)-N isolated→ Secondary NIC attached to libvirt networkisolated-f→ Force a fresh download of the base image even if it already exists-r→ Recreate: destroy an existing VM of the same name and its volumes, then rebuild
Set the LIBVIRT_DEFAULT_URI environment variable:
export LIBVIRT_DEFAULT_URI="qemu+ssh://user@hypervisor/system"bin/remove-vm.sh -d <VM_NAME>virsh vol-delete --pool vm-pool --vol source-ubuntu22.04.imgThe next VM deployment will download a fresh image.
bin/list-vm-ips.shPrints a table of running VMs with their state, hostname (<name>.<DOMAIN>), and
DHCP-leased IP address on the primary network. VMs without a lease yet show
pending.
bin/diagnose-vm-network.sh <vm-name> [vm-name ...]Shows, per VM, how libvirt can discover its networking: domain state, whether the
QEMU guest agent responds, the interface list (domiflist), guest-reported
addresses (domifaddr), and matching DHCP leases on the primary network.
To resolve VM hostnames locally:
-
Install
libnss-libvirt:sudo apt install libnss-libvirt
-
Edit
/etc/nsswitch.conf, addinglibvirt libvirt_guestto thehostsline:hosts: files libvirt libvirt_guest dns
Now you can SSH to VMs using their hostname.
export LAUNCH_VM_INI="/path/to/custom-launch-vm.ini"
./launch-vm.sh -d ubuntu22.04 -n test-vmUses
/path/to/custom-launch-vm.iniinstead oftemplates/launch-vm.ini.
export CLOUD_CONFIG="/custom/path/cloud-config.yml"
./launch-vm.sh -d ubuntu22.04 -n test-vmUses
/custom/path/cloud-config.ymlinstead oftemplates/cloud-config.yml.
export PYTHON="/usr/bin/python3.12"
./launch-vm.sh -d ubuntu22.04 -n test-vm
virt-installis run under${PYTHON:-/usr/bin/python3}. SetPYTHONif thepython3first on yourPATH(e.g. a Homebrew/pyenv one) lacks the systemgi/libvirtbindings and fails withModuleNotFoundError: No module named 'gi'.
- rotflol (Ronald Offerman) – Testing and contributions.
- Steve Arnold