Remote Support Start download

Proxmox Snippets: Reusable Cloud-Init Config Templates

ProxmoxCloud-InitAutomationVirtualisierung
Proxmox Snippets: Reusable Cloud-Init Config Templates

Cloud-Init has become the de-facto standard in the Proxmox world for turning freshly cloned VMs into productive workloads without manual post-processing. The GUI conveniently handles hostname, IP, SSH keys and the initial password — but anything beyond that, such as packages, services or firewall rules, quickly ends up in hand-maintained YAML files that get copied, forgotten and ultimately drift out of sync. This is exactly where the Proxmox VE Snippets feature comes in: it lets you store arbitrary cloud-init building blocks centrally on a storage and reference them at clone time with a single config switch.

In this article we show how to enable a snippets storage, how to build a reusable web server template and how to cleanly attach it to new VMs. We use Proxmox VE 8.4 with Cloud-Init 24.x as the baseline — the concepts apply to older 8.x releases as well.

What are Proxmox Snippets exactly?

Snippets are a dedicated content type on a Proxmox storage, comparable to iso, vztmpl or backup. While ISO images cover installation and container templates serve LXC, snippets provide small text files consumed by other components — in practice mostly Cloud-Init and hook scripts. The files live in the normal filesystem under /var/lib/vz/snippets/ (for storage type dir) or on a CephFS/NFS mount, which makes them reachable from all cluster nodes provided the storage is shared.

Cloud-Init knows three classic input files that map nicely to snippets:

  • user-data: packages, commands, users, SSH keys, runcmd blocks
  • network-config: VLANs, bonds, static routes beyond what the GUI offers
  • meta-data: instance-id, hostname, occasionally cloud-specific fields

Proxmox normally generates these files itself from the GUI inputs. With snippets you selectively override that behaviour for individual VMs while still being able to use the GUI values as a fallback.

Enabling snippets storage

For a storage to accept snippets, the snippets content type must be enabled. On a single node or small cluster the existing local storage is often fine; in larger environments a CephFS or NFS share is recommended, otherwise snippets have to be synced manually across nodes.

In the GUI open Datacenter -> Storage, pick the storage and tick “Snippets” under “Content”. On the command line it is faster — a look at the central configuration file pve-storage.cfg shows the principle:

# /etc/pve/storage.cfg
dir: local
        path /var/lib/vz
        content iso,vztmpl,backup,snippets
        shared 0

cephfs: cephfs-snippets
        path /mnt/pve/cephfs-snippets
        content snippets,iso
        fs-name cephfs

Or set it directly via CLI:

pvesm set local --content iso,vztmpl,backup,snippets

After enabling, create the directory and check permissions:

mkdir -p /var/lib/vz/snippets
ls -ld /var/lib/vz/snippets

The files should be owned by root and readable and writable by root — nothing more is needed, since qemu-server runs as root.

Building a reusable web server template

Now for the concrete example: a user-data that installs nginx, ships a default page and opens ports 80/443 in the local nftables firewall. We name the file webserver-user-data.yaml and place it under /var/lib/vz/snippets/:

#cloud-config
package_update: true
package_upgrade: true
packages:
  - nginx
  - nftables
  - curl
  - htop

write_files:
  - path: /etc/nginx/sites-available/default
    permissions: '0644'
    content: |
      server {
        listen 80 default_server;
        listen [::]:80 default_server;
        root /var/www/html;
        index index.html;
        server_name _;
        location / { try_files $uri $uri/ =404; }
      }
  - path: /etc/nftables.conf
    permissions: '0755'
    content: |
      #!/usr/sbin/nft -f
      flush ruleset
      table inet filter {
        chain input {
          type filter hook input priority 0; policy drop;
          ct state established,related accept
          iif lo accept
          tcp dport { 22, 80, 443 } accept
          ip protocol icmp accept
        }
      }

runcmd:
  - systemctl enable --now nginx
  - systemctl enable --now nftables
  - nft -f /etc/nftables.conf
  - echo "Provisioned by DATAZONE on $(date)" > /var/www/html/index.html

The #cloud-config line at the very top is mandatory — without that marker, cloud-init will not recognise the file. Also make sure indentation is clean and uses spaces; tabs cause hard-to-spot parser errors.

Attaching the snippet to a VM

With the local storage and the webserver-user-data.yaml file in place, reference the snippet via qm set like this:

# Replace cloud-init user-data
qm set 9001 --cicustom "user=local:snippets/webserver-user-data.yaml"

# Regenerate the cloud-init drive
qm cloudinit update 9001

The --cicustom parameter understands three keys that you can set individually or combined:

KeySnippetTypical content
useruser-datapackages, runcmd, write_files
networknetwork-configVLAN tagging, bonds, multiple NICs
metameta-datainstance-id, hostname logic

Multiple keys are passed comma-separated:

qm set 9001 --cicustom "user=local:snippets/webserver-user-data.yaml,network=local:snippets/dmz-network.yaml"

The GUI values for SSH key, password and IP address stay active and are merged with the snippet — so you do not have to rebuild everything in YAML.

Cluster workflow: templates, clones, pipeline

In practice we combine snippets with classic VM templates. A sensible pipeline looks like this:

  1. Golden image as a template (Debian 12 or Ubuntu 24.04 with the cloud-init package).
  2. Snippet library: one snippet per role (web, db, monitoring, jumphost).
  3. Clone: qm clone 9000 1234 --name web01 --full
  4. Assign snippet: qm set 1234 --cicustom "user=local:snippets/webserver-user-data.yaml"
  5. Start VM: qm start 1234 — the rest happens on first boot.

If you wrap this in an Ansible or Terraform pipeline, you get a reproducible provisioning path without external config-management tooling. For pure container workloads Kubernetes is often overkill — a handful of snippets plus a clear naming convention go a long way.

Real-world pitfalls

Three issues come up over and over in customer projects:

  • Snippet does not run: almost always the #cloud-config line is missing or cloud-init was disabled in the image. Inside the guest cloud-init status --long reveals the status.
  • Changes to the snippet do not apply: cloud-init only runs on first boot. For testing, run cloud-init clean --logs inside the guest and reboot, or regenerate the cloud-init drive in the GUI.
  • Snippet on node A, VM on node B: live migration requires the storage to be shared, otherwise the target node will not find the file. CephFS, NFS or a cron-based sync solve this.

For version control of your snippets, a Git repository synced into /var/lib/vz/snippets/ via cron or webhook is highly recommended. That way changes can be reviewed, rolled back and documented — an important step towards Infrastructure as Code without immediately adopting the full tooling stack.

For more background on cluster architecture and storage choices see our Proxmox consulting and the overview of suitable shared storage setups in our TrueNAS section.

Conclusion

Proxmox Snippets are an unspectacular but extremely useful feature: they close the gap between “the GUI is no longer enough” and “fully automated config management”. If you regularly clone VMs and keep repeating the same post-clone steps, two or three well-maintained snippets buy you back time immediately — and reduce drift between systems as a side effect.

DATAZONE supports you in building reusable Proxmox provisioning — from the snippet catalogue to cluster storage to integration with your existing backup. Get in touch: Contact.

More on these topics:

Need IT consulting?

Contact us for a no-obligation consultation on Proxmox, OPNsense, TrueNAS and more.

Get in touch