<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Automation on rHomelab</title><link>https://rhomelab.com/tags/automation/</link><description>Recent content in Automation on rHomelab</description><generator>Hugo</generator><language>en-US</language><lastBuildDate>Fri, 11 Sep 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://rhomelab.com/tags/automation/index.xml" rel="self" type="application/rss+xml"/><item><title>Proxmox VM Templates: Automating Deployment with Cloud-Init</title><link>https://rhomelab.com/proxmox/cloud-init-vm-templates/</link><pubDate>Fri, 11 Sep 2026 00:00:00 +0000</pubDate><guid>https://rhomelab.com/proxmox/cloud-init-vm-templates/</guid><description>How to build a reusable cloud-init VM template in Proxmox, clone it in seconds instead of running a fresh install every time, and feed it into an Ansible or Terraform workflow.</description><content:encoded><![CDATA[<p>Every homelab operator hits the same wall eventually: you need a fifth Ubuntu VM for some new service, and the thought of walking through another OS installer, setting the hostname, adding your SSH key, and running updates makes you want to just leave the last one running forever instead. Cloud-init templates fix this. Build the template once, and every new VM after that is a clone plus a few lines of config, done in under a minute instead of fifteen. This is the same workflow cloud providers use to spin up instances on demand, and Proxmox supports it natively, no extra tooling required to get the basics working.</p>
<h2 id="what-cloud-init-actually-does">What cloud-init actually does</h2>
<p>Cloud-init is a boot-time provisioning tool baked into most modern Linux cloud images. On first boot, it reads configuration data (hostname, SSH keys, network settings, user accounts, even arbitrary shell commands) from a small data source and applies it, then gets out of the way. It&rsquo;s not a Proxmox feature, it&rsquo;s an industry-standard tool that ships in the official cloud images from Ubuntu, Debian, Rocky, AlmaLinux, and most other distros you&rsquo;d actually run in a homelab. Proxmox&rsquo;s job is just to hand cloud-init its configuration through a virtual CD-ROM device, which it does automatically once you&rsquo;ve set it up on a VM.</p>
<p>The distinction that matters: a cloud image is not the same as an installer ISO. It&rsquo;s a pre-installed, minimal disk image meant to be booted directly, with cloud-init doing the &ldquo;first boot setup&rdquo; work an installer wizard would normally do interactively. You&rsquo;re not installing an OS into a VM here, you&rsquo;re importing an already-installed one and letting cloud-init personalize it.</p>
<h2 id="getting-a-cloud-image">Getting a cloud image</h2>
<p>Each major distro publishes a cloud image built for exactly this purpose, usually in qcow2 format. A few common ones:</p>
<ul>
<li>Ubuntu: <code>https://cloud-images.ubuntu.com/releases/</code> (look for the <code>.img</code> file under the release you want, e.g. <code>jammy-server-cloudimg-amd64.img</code>)</li>
<li>Debian: <code>https://cloud.debian.org/images/cloud/</code> (the <code>generic</code> variant is the right pick for Proxmox)</li>
<li>Rocky Linux: <code>https://download.rockylinux.org/pub/rocky/</code> under the <code>images</code> directory for your release</li>
</ul>
<p>Download it straight onto the Proxmox host, in whatever directory you&rsquo;re working from:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">wget https://cloud-images.ubuntu.com/releases/jammy/release/ubuntu-22.04-server-cloudimg-amd64.img
</span></span></code></pre></div><h2 id="building-the-template">Building the template</h2>
<p>This is a one-time process per OS/version combination. Pick a VM ID that&rsquo;s outside your normal allocation range so it&rsquo;s obviously a template at a glance, something like 9000.</p>
<p>Create a bare VM shell first, no disk yet:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">qm create <span class="m">9000</span> --name ubuntu-2204-cloudinit --memory <span class="m">2048</span> --cores <span class="m">2</span> --net0 virtio,bridge<span class="o">=</span>vmbr0
</span></span></code></pre></div><p>Import the downloaded image as the VM&rsquo;s disk, attaching it to your actual storage target:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">qm importdisk <span class="m">9000</span> ubuntu-22.04-server-cloudimg-amd64.img local-lvm
</span></span></code></pre></div><p>Attach that imported disk to the VM as a SCSI device, and set the SCSI controller to VirtIO SCSI for the best performance:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">qm <span class="nb">set</span> <span class="m">9000</span> --scsihw virtio-scsi-pci --scsi0 local-lvm:vm-9000-disk-0
</span></span></code></pre></div><p>Add the cloud-init drive itself, a small virtual CD-ROM Proxmox uses to hand configuration to the VM on boot:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">qm <span class="nb">set</span> <span class="m">9000</span> --ide2 local-lvm:cloudinit
</span></span></code></pre></div><p>Set the boot order to the SCSI disk, and add a serial console, which most cloud images expect for their console output:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">qm <span class="nb">set</span> <span class="m">9000</span> --boot <span class="nv">order</span><span class="o">=</span>scsi0 --serial0 socket --vga serial0
</span></span></code></pre></div><p>Resize the disk up from whatever minimal size the cloud image shipped with, cloud images are typically only a couple of gigabytes:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">qm resize <span class="m">9000</span> scsi0 20G
</span></span></code></pre></div><p>Finally, convert it to a template:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">qm template <span class="m">9000</span>
</span></span></code></pre></div><p>That VM ID is now locked as a template, it won&rsquo;t boot directly anymore, only clone. This whole sequence takes a few minutes once and never again for that OS version.</p>
<h2 id="cloning-and-configuring-new-vms">Cloning and configuring new VMs</h2>
<p>With the template built, spinning up an actual VM is two steps: clone, then set cloud-init values before first boot.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">qm clone <span class="m">9000</span> <span class="m">201</span> --name web01 --full
</span></span><span class="line"><span class="cl">qm <span class="nb">set</span> <span class="m">201</span> --ciuser todd --cipassword &lt;hashed-or-plaintext&gt; --sshkeys ~/.ssh/id_rsa.pub
</span></span><span class="line"><span class="cl">qm <span class="nb">set</span> <span class="m">201</span> --ipconfig0 <span class="nv">ip</span><span class="o">=</span>10.0.50.21/24,gw<span class="o">=</span>10.0.50.1
</span></span><span class="line"><span class="cl">qm start <span class="m">201</span>
</span></span></code></pre></div><p><code>--full</code> does a full independent copy of the disk rather than a linked clone. Linked clones (drop <code>--full</code>) are faster and use less disk space since they reference the template&rsquo;s base image instead of copying it, but that means the template can never be deleted or modified while linked clones exist against it, and every linked clone depends on that base image staying intact. Full clones are the safer default for anything you&rsquo;re not spinning up and tearing down constantly; reach for linked clones specifically when you&rsquo;re doing rapid disposable testing where the extra dependency is worth the speed and disk savings.</p>
<p><code>--ipconfig0</code> accepts either a static address as shown, or <code>ip=dhcp</code> if you&rsquo;d rather let your network hand out the address. For SSH keys, point <code>--sshkeys</code> at a public key file, cloud-init injects it into the default user&rsquo;s <code>authorized_keys</code> on first boot, which is the whole point: you get key-based SSH access immediately, no password prompt, no manual key copying.</p>
<p>The Proxmox web UI exposes all of this under the VM&rsquo;s <strong>Cloud-Init</strong> tab too, if you&rsquo;d rather click through it than run commands, useful the first few times until the CLI sequence is muscle memory.</p>
<h2 id="things-that-catch-people-the-first-time">Things that catch people the first time</h2>
<p><strong>The disk resize doesn&rsquo;t grow the filesystem by itself.</strong> <code>qm resize</code> grows the underlying virtual disk, but the partition and filesystem inside the guest still need to grow into that new space. Cloud images handle this automatically via <code>cloud-init</code>&rsquo;s <code>growpart</code> and <code>resizefs</code> modules, which run on first boot and expand the root filesystem to fill the disk, so in practice this isn&rsquo;t something you do manually. It&rsquo;s worth knowing it&rsquo;s happening so a VM that looks like it&rsquo;s taking an extra few seconds on first boot isn&rsquo;t a sign anything&rsquo;s wrong.</p>
<p><strong>Install the QEMU guest agent inside the template before converting it, not after.</strong> Without it, Proxmox can&rsquo;t report the VM&rsquo;s actual IP address in the web UI, can&rsquo;t do a clean guest shutdown, and can&rsquo;t quiesce the filesystem for snapshot-consistent backups. Boot the template VM once before running <code>qm template</code>, install <code>qemu-guest-agent</code>, then shut it down and enable the agent option (<code>qm set 9000 --agent enabled=1</code>) before templating it. Skipping this is the single most common reason people end up confused about why their cloned VMs show no IP in the summary tab.</p>
<p><strong>SSH host keys need regenerating per clone, and cloud-init already does this.</strong> A raw disk clone without cloud-init would copy the exact same SSH host keys into every VM, a real problem since it means every clone is indistinguishable to an SSH client and vulnerable to key confusion. Cloud-init&rsquo;s default behavior regenerates host keys on first boot specifically to avoid this, one more reason to let cloud-init handle first boot rather than skipping it and configuring things by hand afterward.</p>
<p><strong>Don&rsquo;t boot the template VM after converting it.</strong> Once <code>qm template</code> has run, that disk is meant to be read from, not written to, every clone depends on it staying exactly as it was templated. If you need to update it (newer package versions baked in, a different base config), build a fresh template rather than un-templating and modifying the existing one.</p>
<h2 id="feeding-this-into-real-automation">Feeding this into real automation</h2>
<p>Once the template exists, <code>qm clone</code> plus <code>qm set</code> is trivial to script or wrap in Ansible using the <code>community.general.proxmox</code> modules, or drive through Terraform with the <code>bpg/proxmox</code> or <code>Telmate/proxmox</code> providers, both of which have first-class support for cloning from a template and setting cloud-init parameters as part of the resource definition. That&rsquo;s the real payoff here: once the template step is done, provisioning a new VM stops being a manual task at all, it becomes a few lines in whatever infrastructure-as-code setup you&rsquo;re already running for the rest of your homelab, consistent with how you&rsquo;d provision anything else.</p>
<h2 id="bottom-line">Bottom line</h2>
<p>The template build is a one-time cost per OS image, maybe fifteen minutes the first time through, and every VM after that drops from a full manual install down to a clone and a couple of <code>qm set</code> commands. If you&rsquo;re still walking through an installer wizard for every new VM in your homelab, cloud-init templates are the highest-leverage automation step available before you touch Ansible or Terraform at all, and they compose cleanly with both once you&rsquo;re ready to go further.</p>
]]></content:encoded></item></channel></rss>