Skip to main content
RunBook Academy

Proxmox VEX · LXC ContainersContainers as workloads

LXC templates, custom images, and the template workflow

Intermediate⏱ ~18 min🧪 Lab required

What you'll learn

  • Use the template workflow to deploy many identical containers fast
  • Create and register a custom LXC template for your org
  • Convert an existing container into a template via PVE
  • Manage template updates without breaking running containers

Prerequisites

Verified against Proxmox VE 9.2.4 · Proxmox Backup Server 4.2.5 · Ceph Squid / Tentacle · Debian 13 (Trixie) · Linux kernel 7.0 (PVE 9.2 default) · 2026-08-07

Not yet marked complete on this device.

LXC templates, custom images, and the template workflow

LXC containers are most powerful when you can deploy many identical copies quickly — that’s the template workflow. PVE supports two template patterns:

  • Distro templates — pre-built images you download from the PVE template store (turnkey Linux, Ubuntu, Debian, Alpine, etc.).
  • Custom templates — you build a container once, convert it to a template, and clone from it.

This lesson walks through both, plus the operational pattern for keeping templates updated without breaking running containers.

Using a distro template

# List templates available in the configured storage
pveam available
# Or filter by section
pveam available --section system
pveam available --section turnkey

# Download a template (example: Debian 12 standard)
pveam download local debian-12-standard_12.2-1_amd64.tar.zst

# Verify the download
pveam list local
# local:vztmpl/debian-12-standard_12.2-1_amd64.tar.zst  118.41MB

Create a container from the template:

pct create 100 local:vztmpl/debian-12-standard_12.2-1_amd64.tar.zst \
  --hostname web-01 \
  --memory 1024 \
  --cores 2 \
  --net0 name=eth0,bridge=vmbr0,ip=dhcp \
  --rootfs local:8 \
  --features nesting=1 \
  --ostype debian \
  --unprivileged 1 \
  --password $(openssl rand -base64 12)

pct start 100

The container starts in seconds (no kernel boot). It’s running the same Debian image that thousands of other Debian containers run.

Customising the template

Before converting a container to a template, install the packages and configuration you want every clone to inherit:

pct enter 100
# Now inside the container

apt update && apt -y upgrade
apt install -y \
  ca-certificates \
  curl \
  vim \
  htop \
  prometheus-node-exporter \
  logrotate \
  fail2ban

# Set timezone to UTC (or your preferred timezone)
timedatectl set-timezone UTC

# Enable prometheus-node-exporter to listen on all interfaces
sed -i 's/--web.listen-address=127.0.0.1:9100/--web.listen-address=0.0.0.0:9100/' \
  /lib/systemd/system/prometheus-node-exporter.service

# Set up a non-root user with sudo
useradd -m -s /bin/bash deploy
echo 'deploy ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/deploy
mkdir -p /home/deploy/.ssh
chmod 700 /home/deploy/.ssh

# Pre-stage SSH host keys so every clone gets fresh, valid keys
rm -f /etc/ssh/ssh_host_*
dpkg-reconfigure -f noninteractive openssh-server

# Clean up
apt -y autoremove
apt clean
rm -rf /var/lib/apt/lists/*
history -c

exit

The history -c is important: clones should not inherit your bash history. The dpkg-reconfigure openssh-server regenerates host keys on first boot (or you can do it via a first-boot hook).

Convert to template

Two patterns:

1. PVE-managed template via clone

PVE’s pct template marks a container as a template. Subsequent clones copy the template’s filesystem without running it.

# Stop the container, then mark it as a template
pct stop 100
pct template 100

# Now clone from it
pct clone 100 200 --hostname web-02 --full 0
pct clone 100 201 --hostname web-03 --full 0
pct clone 100 202 --hostname web-04 --full 0

--full 0 does a linked clone (snapshot-based, near-instant). For fully independent copies, use --full 1.

2. Export as a tar.zst template

For templates you want to share across clusters or store in the PVE template repository:

# Export the container's filesystem as a template
vzdump 100 --mode stop --dumpdir /tmp/template-export --compress zstd
# This creates /tmp/template-export/vzdump-lxc-100-*.tar.zst

# Or use pct directly
pct stop 100
# Manual export via tar
pct mount 100
tar -C /var/lib/lxc/100/rootfs -czf /tmp/debian-custom.tar.zst --numeric-owner .
pct unmount 100
pct destroy 100

# Import into PVE template storage
pveam import local /tmp/debian-custom.tar.zst
pveam list local

The exported template can now be used like any distro template: pct create 200 local:vztmpl/debian-custom.tar.zst.

Updating templates

Templates are versioned implicitly by their hash, not by a version number. To roll out an updated template:

  1. Clone the existing template container.
  2. Update it (apt upgrade, new packages, config changes).
  3. Test thoroughly.
  4. Stop and re-mark as template (the old template CT is destroyed).
# Clone the template to a working CT
pct clone 100 900 --hostname template-v2 --full 1

# Update
pct enter 900
apt update && apt -y upgrade
apt install -y <new-package>
# ... make changes ...
history -c
exit

# Test
pct start 900
# ... do whatever testing you need ...
pct stop 900

# Promote to template
pct destroy 100
pct template 900

All clones from the new template get the updated packages. Running containers are unaffected — they have their own filesystems.

For templates stored as tar.zst, the workflow is:

  1. Update the working container.
  2. Stop it.
  3. Re-export via the mount/tar pattern above.
  4. Import with pveam import local <path> (with --force to overwrite the existing template of the same name).

A reasonable template structure

For an org that runs 50+ containers, here’s the template hierarchy that works in practice:

base-debian-12        # Plain Debian + common packages
├── base-debian-12-corp # corp + hardening (fail2ban, auditd, ssh config)
│   ├── web-nginx      # corp + nginx + logrotate
│   ├── web-caddy      # corp + caddy + logrotate
│   ├── app-python     # corp + python + pip + virtualenv
│   ├── app-node       # corp + node + npm
│   ├── app-go         # corp + go + build essentials
│   ├── db-postgres    # corp + postgres + tuning
│   ├── db-redis       # corp + redis
│   └── db-mysql       # corp + mysql + tuning

Each template is a child of the previous one. Updates flow downward: patch base-debian-12, then re-derive the corp template, then each app template. Use pct clone with --full 1 to make the parent → child copy.

For 50 containers you don’t need 50 templates. Three or four well- chosen templates cover 90% of use cases.

Common mistakes

  • Storing secrets in the template. SSH host keys (regenerate with dpkg-reconfigure), user passwords, API tokens. Use a configuration management tool (Ansible, Salt, cloud-init) to inject per-instance secrets at first boot.
  • Forgetting to apt clean. The template’s filesystem grows by hundreds of MB if you don’t apt clean before converting. Each clone inherits that bloat.
  • Holding the template as a running container. A template is not supposed to run. Stop it before pct template — PVE will refuse if it’s running.
  • Updating the template but not the clones. Clones have their own filesystems. They don’t auto-update. Use unattended-upgrades or a config-management tool inside the clones.

Production considerations

  • Template storage location. PVE templates live on a storage backend. If you store them on local storage, only that node has them. For a cluster, put templates on shared storage.
  • Template versioning. PVE doesn’t version templates. If you need version pinning, store templates as tar.zst files in version control (git lfs, or a git-annex repo).
  • Cloning speed. Linked clones (--full 0) take milliseconds. Full clones (--full 1) copy the entire filesystem; on SSD, expect 5–20 seconds per GB.
  • Container density. LXC containers share the host kernel and memory. A 4 GB host can run 50 small containers; 100 containers need 16 GB+.

Key takeaways

  • Use pveam for distro templates, pct template for custom templates.
  • Always clean (apt clean, history -c) before templating.
  • Templates aren’t running containers — stop them first.
  • Plan template hierarchy: base → hardened → app-specific.

Knowledge check

Knowledge check · 4 questions

  1. Q1. What is the difference between pct template and pveam import?

  2. Q2. Linked clones (pct clone --full 0) share storage with the template.

  3. Q3. Which of these should you do BEFORE converting a container to a template? (Select all that apply)

  4. Q4. Name the command that downloads a template into PVE local storage.

Passing score: 75%. Answers are checked in this browser.