Deploying LXC containers at scale on Proxmox: cloud-init alternatives

Every time I spin up a new LXC on Proxmox, I ask myself the same question: why can’t I just hand it a cloud-init file like I do with VMs? The short answer is that cloud-init is a VM-only feature in Proxmox. LXC containers don’t support it natively. But there are several approaches that get you to the same place — or better.

This post covers the options, the trade-offs, and what I actually use for a fleet of Ubuntu + Docker + Docker Compose + Dockge containers.

Why cloud-init doesn’t apply to LXC

Cloud-init works by injecting a config drive into a VM at boot time. The VM kernel sees it as a block device and the cloud-init service reads it before anything else starts. LXC containers don’t have their own kernel — they share the host’s — so there is no boot sequence of that kind and nowhere to inject a drive. Proxmox itself never implemented a substitute.

The alternatives

1. Hook scripts (native Proxmox)

Proxmox supports hook scripts on any container, triggered at four lifecycle points: pre-start, post-start, pre-stop, post-stop. The script runs on the host, not inside the container, but you can combine it with pct exec to run commands inside:

A post-start hook that calls pct exec 200 -- bash /provision/setup.sh is the closest native equivalent to a cloud-init user-data script. The downside is that the script runs on every start, not just the first one, so you need to add your own “already provisioned” guard.

2. pct exec provisioning

You can drive the whole setup from the Proxmox host over pct exec without SSH:

This works well for a one-off but doesn’t scale. Running the same ten commands on twenty containers by hand defeats the point.

3. distrobuilder

distrobuilder is Canonical’s tool for building LXC/LXD images from a declarative YAML manifest. It is the closest 1:1 analogy to cloud-init for LXC: you describe the OS, the packages, the files and the scripts, and the output is a .tar.zst template ready to import into Proxmox.

The result is a fully baked template: every container cloned from it already has Docker, Docker Compose and Dockge installed. No provisioning step at start time.

4. Golden template + clone

The manual version of distrobuilder: build one LXC by hand (or with a script), install everything you need, then convert it to a template with pct template. Cloning is near-instant and the clone is ready to start.

Simple and fast, but updating the template means rebuilding it by hand and re-cloning. Fine for a stable base image; painful if the “template” keeps changing.

5. Terraform with bpg/proxmox

The bpg/proxmox provider (more feature-complete than the older Telmate one) supports LXC with full resource declaration:

After creation you add a null_resource with a remote-exec provisioner to run your setup script over SSH. Or you point it at a pre-built template instead of a raw rootfs, and the provisioner only needs to handle the per-container config (hostname, IP, Dockge stack paths).

6. Ansible

The community.general.proxmox module creates containers, and Ansible’s community.general.pct connection plugin lets you run roles against them over pct exec without SSH. If you’re already in the Ansible world this fits naturally. If you’re not, it’s a lot of scaffolding for what is essentially a parameterised shell script.

What I actually use

For a fleet of identical Docker/Dockge containers, the combination that makes the most sense is:

  1. distrobuilder (or a manually built golden template) for the base image: Ubuntu, Docker, Docker Compose, Dockge, SSH key, sensible defaults. Build it once. Update it when the base changes.
  2. Terraform with bpg/proxmox for the per-container config: clone the template, set the hostname, IP and resources, done. The whole fleet is a terraform apply.

That’s as close as you get to the cloud-init experience on LXC: a declarative image definition and a declarative infrastructure layer on top of it.

Summary

Approach Cloud-init analogy Best for
Hook scripts Post-boot triggers Simple per-container tasks
pct exec provisioning User-data scripts One-off automation
Golden template + clone Pre-baked image Stable base, manual process
distrobuilder Image baking (declarative) Reproducible base templates
Terraform bpg/proxmox IaC declarative Fleet management
Ansible over pct exec Configuration management Existing Ansible shops

One thing that is explicitly not on the list: the Proxmox helper scripts. They work and they’re convenient for a single install, but they’re not reproducible and not version-controlled. A fleet of twenty containers built from helper scripts is twenty containers you can’t redeploy consistently.

Leave a comment