Build a Proxmox Cloud-Init Template for Fast LXC Deploy
Build a Proxmox cloud-init template once and deploy identical SSH-ready LXC containers in just seconds with static IPs, users, and automated provisioning.

On this page
Cloud-init templates let you define a Proxmox LXC container once and deploy identical, networked, SSH-ready copies in seconds. Here's how to build one on Proxmox VE and turn repetitive container creation into a repeatable, scripted process.
Key Takeaways
- One image, many clones — Build a cloud-init template once, then deploy identical containers with a single command.
- First boot does the work — Hostnames, users, SSH keys, and packages are injected automatically when each clone boots.
- Unprivileged by default — Keep containers unprivileged for isolation and assign static IPs plus a nameserver at clone time.
- Templates are immutable — Treat the template as a golden image; to change behavior, rebuild the template instead of editing a running clone.
Why bother with cloud-init templates at all?
Most people create an LXC container by opening the web UI, clicking Create Container, and filling in fields one by one. That's fine for a single box. It gets tedious fast when you need five identical app servers, and it's error-prone because every click is a chance to mistype an IP or forget to add your SSH key.
Cloud-init solves this. A cloud-init template is a base container that ships with the cloud-init package installed. When you clone it, Proxmox injects your network settings, users, SSH keys, and arbitrary startup commands into the new container on first boot. You end up with a fully provisioned, login-ready instance without ever touching its console.
This is where containers really earn their keep. Unlike a KVM VM, an unprivileged LXC shares the host kernel and starts in roughly 20–30 seconds, which is exactly the behavior you want when you're trying to avoid a DDR5 RAM surge by right-sizing your workloads. If you've ever converted an old laptop into a Proxmox home server, you know how much you want to squeeze every gigabyte of RAM out of limited hardware — containers deliver that without the overhead of a full guest kernel.
On Proxmox VE 8.x, cloud-init templates are a stable, built-in feature. The base Debian 12 template downloads to roughly 130 MB and unpacks into a rootfs that fits comfortably inside a 4 GB disk.
How to build a reusable cloud-init template
1. Create a container from the base image
Pull a base container using the cached template tarball. The exact filename depends on what you downloaded under Administration → CT Templates, so adjust it to match your system.
pct create 100 /var/lib/proxmox/templates/cache/debian-12-standard_12.5-1_amd64.tar.gz \
--unprivileged 1 \
--hostname cloud-init-base \
--memory 2048 \
--swap 512 \
--cores 2 \
--disk 4 \
--net0=name=eth0,bridge=vmnet,ip=dhcp \
--firewall 0
Note that bridge=vmnet is just an example — use the bridge name that actually exists on your host. Starting with ip=dhcp is fine here, since you'll pin a static IP when you clone.
2. Install cloud-init
Ubuntu images ship cloud-init by default, but Debian does not. Install it inside the container before you turn it into a template:
pct exec 100 -- bash -lc 'apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y cloud-init'
3. Convert the container to a template
Converting to a template is a GUI action in the current UI: right-click the container in the node's container list and choose Convert to Template. There's no equivalent pct flag for it, so do it through the web UI.
Once converted, container 100 becomes your golden image. Everything you change afterward — installing packages, dropping in config files — becomes part of every future clone.
How to deploy containers from the template
The simplest possible deploy just clones the template and assigns networking:
pct clone 100 200 --name app01 \
--ipconfig0=ip=192.168.1.50/24,gw=192.168.1.1 \
--nameserver=192.168.1.1 \
--onboot 1
--ipconfig0 sets the static IP and gateway, --nameserver configures DNS resolution, and --onboot 1 makes the container start automatically when the host reboots.
When container 200 boots, cloud-init reads those injected settings and finishes provisioning. You can watch it boot with pct console 200, or just SSH in once the IP answers. To inspect first-boot progress later, tail /var/log/cloud-init.log inside the container.
Cloud-init template vs. manual create vs. KVM
| Deployment method | Best for | Kernel isolation | Re-deployable | Typical first-boot time |
|---|---|---|---|---|
| Cloud-init LXC template | Repeatable container fleets | Shared host kernel | Rebuild template for changes | ~20–30 s |
Manual pct create / wizard |
One-off containers | Shared host kernel | Edit settings each time | ~20–30 s |
| KVM VM | Full isolation, own kernel | Hypervisor (KVM) | Reconfigure in place | ~30–60 s |
If you only ever need one container, the manual route is perfectly adequate. The template pays off the moment you realize you're about to repeat the same steps for the third time.
How to inject custom user-data
Static IPs get you connected, but real automation happens through a custom user-data file. Create it on the Proxmox host first:
cat > /root/user-data-app01.yaml <<'EOF'
#cloud-init
hostname: app01
manage_hosts: true
users:
- name: admin
groups: sudo
shell: /bin/bash
sudo: ALL=(ALL) NOPASSWD:ALL
ssh_authorized_keys:
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI your-key@example-laptop
packages:
- ca-certificates
- curl
- git
write_files:
- path: /etc/cloud/provisioned
owner: root:root
content: |
provisioned-by: cloud-init
host: app01
EOF
Then clone the template and point Proxmox at that file with --cicustom:
pct clone 100 200 --name app01 \
--ipconfig0=ip=192.168.1.50/24,gw=192.168.1.1 \
--nameserver=192.168.1.1 \
--cicustom=response=/root/user-data-app01.yaml \
--onboot 1
The --cicustom=response= flag swaps in your user-data as the cloud-init "response" datasource. Networking still comes from --ipconfig0, so you get both your custom user setup and a working IP. Just keep any network: section out of the file, or it will override the IP you set with --ipconfig0.
Gotchas I hit along the way
- cloud-init only runs once. It fires on the first boot of each clone, then never again. If you deploy a container and then edit the template, existing clones won't pick up the change — you have to rebuild the template. This bit me the first time: I converted the container to a template before installing cloud-init, and every clone silently ignored my user-data because nothing was there to read it.
- A typo'd IP locks you out. If you mistype the
--ipconfig0gateway, you may not be able to SSH in to fix it. I pin the IP at clone time and ping it before trusting the deployment. - Custom user-data can shadow networking. As mentioned, a
network:block in your user-data overrides--ipconfig0. Leave networking to Proxmox unless you specifically want cloud-init to manage it. - The unprivileged tradeoff. You gain strong isolation — a compromised container can't load kernel modules or touch the host kernel — but you also lose low-level control. For most application workloads that's a fair exchange. If you genuinely need kernel access, use a KVM VM instead.
Automating the rest of the lifecycle
Once a template works, cloning several containers at once is a quick loop:
for id in 201 202 203; do
pct clone 100 $id --name app-$id \
--ipconfig0=ip=192.168.1.$id/24,gw=192.168.1.1 \
--nameserver=192.168.1.1 \
--onboot 1
done
With a handful of templates and a few scripts, your container fleet is effectively self-service. When the fleet grows past a handful of boxes, promote those scripts into Ansible full-VM playbooks so provisioning, changes, and rollbacks live in version control.
Two things pair naturally with this setup. For naming, rather than hand-editing /etc/hosts on every machine, a DNS automation tool like dnsweaver can manage records across your VMs, containers, and Docker workloads in one place — handy once you have a dozen hosts that all need to resolve each other. And since these are your production workloads, back them up: Automated Backups with Proxmox Backup Server is the natural complement, giving you encrypted, versioned snapshots you can restore from.
Conclusion
Cloud-init templates convert click-by-click LXC creation into a repeatable process: build one base image, clone it, and get SSH-ready containers in seconds. Your next step is to build a single Debian or Ubuntu template, deploy two clones with static IPs, and watch cloud-init finish the provisioning for you. From there, wrap the clone commands in a script or an Ansible playbook to scale the whole fleet.


