cloud-init is a popular mechanism for initializing cloud server images. It is supported by most major Linux distributions and is available with most public cloud providers. I use it on a self-hosted Proxmox node with an Ubuntu Cloud image. I am also installing the following packages automatically with cloud-init:

Instructions

Choose your Ubuntu Cloud Image

Download Ubuntu Cloud Image.

wget https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img

Create a new VM. Change specs as needed.

qm create 9000 --memory 2048 --core 2 --name ubuntu-noble --net virtio,bridge=vmbr0

Import the downloaded Ubuntu img to local-lvm storage (this is for Proxmox 8.2 and below).

qm importdisk 9000 noble-server-cloudimg-amd64.img local-lvm

For Proxmox 8.2 and above use this instead.

qm disk import 9000 noble-server-cloudimg-amd64.img local-lvm

Attach the new disk to the VM as a scsi controller.

qm set 9000 --scsihw virtio-scsi-pci --scsi0 local-lvm:vm-9000-disk-0

Add the cloud init drive.

qm set 9000 --ide2 local-lvm:cloudinit

Make the cloud init drive bootable and restrict BIOS to boot from disk only.

qm set 9000 --boot c --bootdisk scsi0

Add serial console.

qm set 9000 --serial0 socket --vga serial0

Enable qemu guest agent.

qm set 9000 --agent enabled=1,freeze-fs-on-backup=1,fstrim_cloned_disks=1

Enable snippets storage in Proxmox GUI.

Create cloud init vendor config.

var/lib/vz/snippets/9000-civendor.yaml
#cloud-config
 
# Updates
package_update: true
package_upgrade: true
 
# Install packages
packages:
	- qemu-guest-agent
	- git
	- nfs-common
 
 
runcmd:
	# Start qemu-guest-agent
	- systemctl start qemu-guest-agent
	# One-command install, from https://tailscale.com/download/
	- ['sh', '-c', 'curl -fsSL https://tailscale.com/install.sh | sh']
	# Set sysctl settings for IP forwarding (useful when configuring an exit node)
	- ['sh', '-c', "echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.d/99-tailscale.conf && echo 'net.ipv6.conf.all.forwarding = 1' | sudo tee -a /etc/sysctl.d/99-tailscale.conf && sudo sysctl -p /etc/sysctl.d/99-tailscale.conf" ]
	# Generate an auth key from your Admin console
	# https://login.tailscale.com/admin/settings/keys
	# and replace the placeholder below
	- ['tailscale', 'up', '--authkey=tskey-REPLACE_ME']
	- # (Optional) Include this line to make this node available over Tailscale SSH
	- ['tailscale', 'set', '--ssh']
 
# Set timezone
timezone: "Europe/Brussels"

Add this vendor config to your VM.

qm set 9000 --cicustom vendor=local:9000-civendor.yaml

DO NOT START THE VM

Change specs and/or cloud init config if needed.

Then create the template.

qm template 9000

Troubleshooting

If you need to reset your machine-id.

sudo rm -f /etc/machine-id
sudo rm -f /var/lib/dbus/machine-id

Then shut it down and do not boot it up. A new id should be generated the next time it boots.

Source

Perfect Proxmox Template with Cloud Image and Cloud Init | Techno Tim Install Tailscale with cloud-init · Tailscale Docs