Golden-Image VM Provisioning Cloning one cloud-init template into consistent VMs: per-VM config injection, grow-on-first-boot, and a clean teardown flow. Why golden images There are ~15 VMs in this lab. Not one of them was installed from an ISO by hand. Every single one is a clone of a single golden image — one carefully prepared template — customised at first boot by cloud-init . The alternative (install Ubuntu by hand, fifteen times) is slow, and worse, it's inconsistent : little differences creep in between boxes and become the bugs you can't reproduce. A golden image makes every VM start from the exact same known-good baseline. one template (VM 9999) --clone--> VM 107 (becomes K8s-Master) --clone--> VM 112 (becomes K8s-NFS) --clone--> ... every VM in the lab Why we use this: consistency and speed. Every VM is identical at birth, so "it works on that box but not this one" stops being a mystery. And spinning up a new, fully-configured VM goes from a 30-minute install to a 30-second clone. This is the same idea as a base container image — build once, stamp out many. The template The template is one VM (ID 9999 ) prepared once: a current Ubuntu cloud image, the QEMU guest agent, the logging agent, sensible defaults — and then turned into a Proxmox template so it can only be cloned, not run directly. A few things are deliberately baked into the template so every clone inherits them: The logging agent (Promtail) — so every VM ships its logs from the moment it exists. Boot-on-start and a protection flag — VMs come up when the host boots, and can't be accidentally destroyed. Cloud-init enabled — the mechanism that lets one image become many different machines. Everything machine-specific — hostname, network address, credentials — is intentionally not in the template. That's the job of cloud-init at first boot. Why we use this: put the common things in the image and the unique things in per-VM config. A template that's too specific can't be reused; a template that's too bare makes every clone a chore. The line is: bake what's identical everywhere, inject what differs. Injecting per-VM identity with cloud-init Cloning gives you fifteen identical machines. Cloud-init is what makes each one itself on first boot — its hostname, its IP, its login. The flow for creating a VM here is a small, repeatable recipe: 1. clone the template (9999) -> new VM with the next free ID 2. set its resources (CPU / RAM / disk) 3. attach a per-VM cloud-init snippet: - hostname = the VM's name - static IP = 10.100.100. (the address convention) - the login password = 4. start it -> cloud-init applies all of the above on first boot The address convention does a lot of quiet work: a VM's IP is simply its Proxmox ID minus 100, so the snippet can compute the address with no lookup, and a human can always map ID to IP in their head. The snippet is a small templated file — placeholders for hostname and password get filled in per VM, attached for the first boot, then removed afterward so no per-VM secret lingers on the host. Why we use this: cloud-init is the standard way to turn a generic image into a specific machine, on every platform from a laptop VM to a cloud fleet. Learning it here, on Proxmox, is the same skill you'd use to bootstrap a thousand cloud instances. Diagram Grow-on-first-boot, and a clean teardown Two finishing touches make the recipe pleasant to live with. Disks grow to fit. The template's disk is small. When you clone and resize the new VM's disk to, say, 250 GiB, cloud-init's growpart / resize on first boot expands the filesystem to fill it automatically. You never manually grow a partition. Teardown is tidy. After first boot succeeds, the temporary per-VM cloud-init snippet is detached and deleted. Nothing machine-specific is left lying around on the host. And because every VM is just a clone plus a snippet, destroying one is genuinely clean — there's no hand-crafted state to mourn. clone -> resize disk -> attach snippet -> boot (grows + configures) -> verify -> detach + delete snippet (no leftovers) Lesson learned: make provisioning and de-provisioning both boring. It's easy to automate creating a VM and forget that the throwaway bits (snippets, temp credentials) should be cleaned up too. A create flow that leaves no residue is what lets you treat VMs as cattle, not pets. Lessons on provisioning One golden image, cloned many times. Consistency first; it kills "works on that box" bugs. Bake the common, inject the unique. Shared tooling in the template; hostname/IP/credentials via cloud-init. Have an address convention. "IP = VM-ID minus 100" removes a whole class of lookups and mistakes. Let disks grow themselves on first boot rather than resizing by hand. Clean up the throwaway pieces (snippets, temp secrets) as part of the flow, not as an afterthought. The payoff: a brand-new, fully-configured, log-shipping VM in well under a minute — repeatable, identical, and disposable.