Proxmox: Ubuntu cloud-init templates with static addresses

In short. Import the Ubuntu cloud image into a VM with virtio-scsi and a cloud-init drive, convert it to a template, and clone it. Each clone gets qm set with --ipconfig0 for the static address and .254 gateway, --nameserver pointing at the tenant's .2, the ops SSH key, and a --net0 with the deterministic MAC and VLAN tag. Verify through the guest agent or the serial console, not by hoping.

Goal: a VM with a static IP from an Ubuntu cloud image, no DHCP

You want qm clone and one qm set to produce a VM that boots with the right address on the right VLAN. This is for Proxmox VE 8.4 with Ceph Reef as the RBD storage; the commands are unchanged on 9.x.

What you need

Steps

  1. Download the image and, optionally, install the guest agent into it before import. Ubuntu cloud images do not ship with qemu-guest-agent, and without it qm guest cmd has nothing to talk to.
cd /mnt/cephfs/isos
wget https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img
virt-customize -a noble-server-cloudimg-amd64.img --install qemu-guest-agent
  1. Create the VM shell. Serial console as display, because cloud images log to the serial port and the VGA console shows a blank screen for the first minute.
qm create 9000 --name ubuntu-2404-tmpl --ostype l26 --cpu host --cores 2 --memory 2048 \
  --scsihw virtio-scsi-single --agent enabled=1 --serial0 socket --vga serial0 \
  --net0 virtio,bridge=vmbr0
  1. Import the image as the first disk, grow it, add the cloud-init drive and set the boot order.
qm set 9000 --scsi0 rbd-guests:0,import-from=/mnt/cephfs/isos/noble-server-cloudimg-amd64.img,discard=on
qm disk resize 9000 scsi0 20G
qm set 9000 --ide2 rbd-guests:cloudinit
qm set 9000 --boot order=scsi0
  1. Set the template-wide cloud-init values: the login user and the ops key. These are inherited by every clone.
qm set 9000 --ciuser ops --sshkeys /root/ops.pub
qm template 9000
  1. Clone it. A full clone decouples the guest from the template so the template can be rebuilt later.
qm clone 9000 111 --name web01 --full
  1. Set the per-guest values from the host plan: NIC with the deterministic MAC and VLAN tag, static address with gateway, nameserver and search domain.
qm set 111 --net0 virtio=BC:24:11:65:01:0B,bridge=vmbr0,tag=101
qm set 111 --ipconfig0 ip=10.1.101.11/24,gw=10.1.101.254
qm set 111 --nameserver 10.1.101.2 --searchdomain t1.sitea.internal
  1. Inspect what cloud-init will receive, then start the VM.
qm cloudinit dump 111 network
qm start 111

Verify it worked

The guest agent is the quickest check once the image includes it:

qm guest cmd 111 network-get-interfaces | grep -A2 '"ip-address"'

Without the agent, open the serial console with qm terminal 111, log in as ops with the key (or a --cipassword you set for emergencies), and run ip -br addr and resolvectl status. From the tenant's DNS container at .2, ping 10.1.101.11 and ssh ops@10.1.101.11 close the loop. The switch should show BC:24:11:65:01:0B on VLAN 101, which is the point of a derived MAC: you can read it off the switch and know what it is.

Gotchas

FAQ

Why a deterministic MAC rather than letting Proxmox generate one? Because the MAC encodes VLAN, site and host, a DHCP lease, a switch MAC table entry or a packet capture is readable without a lookup. It also means a restored or rebuilt guest has the same MAC, so nothing downstream notices.

Why nameserver .2 and not the gateway? The tenant's .2 is a container running PowerDNS and Kea for that VLAN. The firewall at .254 routes and filters; it does not resolve. Pointing guests at .2 keeps DNS inside the tenant and keeps the firewall out of the resolution path.

Can the template live on local storage since it is read-only? No. A linked clone references the template's disk, and a full clone still has to read it from the node doing the clone. On RBD the template is visible from every node, and the rule that no guest disk lives on local-zfs has no exceptions.

Related