Automated VM template creation for Proxmox using HashiCorp Packer and Ansible.
- Introduction
- Supported Operating Systems
- Quick Start
- Requirements
- Configuration Guide
- Building VM Templates
- Docker Usage
- Advanced Configuration
- Troubleshooting
- Credits
This repository provides infrastructure-as-code tooling for automating the creation of virtual machine templates on Proxmox. Built with HashiCorp Packer and Ansible, these tools enable consistent, repeatable deployment of standardized VM images.
- ✅ Automated VM Template Creation - Build production-ready templates with a single command
- ✅ Multi-Distribution Support - Linux (Ubuntu, RHEL family, Debian, SUSE) and Windows 11
- ✅ Customizable Storage Layouts - Simple partitions, LVM, and CIS-compliant configurations
- ✅ Network Flexibility - Static IP or DHCP configuration
- ✅ Cloud-Init Ready - Modern cloud-init support for rapid provisioning
- ✅ Dual Boot Support - UEFI and BIOS bootloader options
- ✅ Docker Containerized - Consistent builds across Windows, macOS, and Linux
- ✅ Infrastructure-as-Code - Version controlled with HCL2
| Operating System | Version | Custom Storage | Static IP | UEFI | BIOS | VM ID |
|---|---|---|---|---|---|---|
| AlmaLinux | 10, 9, 8 | ✓ | ✓ | ✓ | ✓ | random |
| CentOS Stream | 10, 9 | ✓ | ✓ | ✓ | ✓ | 10013-14 |
| Debian | 12, 11 | ✓ | ✓ | ✓ | ✓ | 10011-12 |
| NixOS | 25.11 | ✓ | ✓ | ✓ | ✓ | 10016 |
| OpenSUSE Leap | 15.6, 15.5 | ✓ | ✓ | ✓ | ✓ | 10009-10 |
| Oracle Linux | 9, 8 | ✓ | ✓ | ✓ | ✓ | 10007-08 |
| Rocky Linux | 10, 9, 8 | ✓ | ✓ | ✓ | ✓ | 10004-06 |
| Ubuntu Server | 26.04, 24.04, 22.04, 20.04 LTS | ✓ | ✓ | ✓ | ✓ | 10000-03, 10015 |
| Windows Desktop | 11 | ✓ | random |
All templates include:
- QEMU Guest Agent for improved VM management
- SSH access with key-based authentication
- Ansible automation user pre-configured
- Cloud-init support (Linux)
- Latest security updates at build time
The build/local account and the Ansible automation account serve different purposes and should not share credentials. Configure a separate SSH key for each account. If password authentication is enabled in a local or homelab configuration, use a different password for each account as well.
Ubuntu 26.04 also provides separate passwordless-sudo controls for these
accounts. Both default to true to preserve the existing automated build
behavior. Set either value to false to require that account to authenticate
when using sudo. Keep the accounts separate when assigning different policies;
if both settings refer to the same username, a passwordless sudo rule still
grants that user passwordless elevation.
// config/linux-ubuntu-26-04-lts.pkrvars.hcl
build_user_passwordless_sudo = true
ansible_user_passwordless_sudo = true- Docker and Docker Compose installed
- Linux host required - Docker Desktop on Windows has networking limitations that prevent Packer HTTP server connectivity
- Access to Proxmox infrastructure (v8.0+)
- Proxmox API token with appropriate permissions
# Linux (required for Docker networking)
git clone https://github.com/traefikturkey/oncall.git
cd oncall
./docker-build.sh setupThis builds the Docker image and creates configuration template files in ./config/.
Edit configuration files in the config/ directory:
Essential configuration files:
proxmox.pkrvars.hcl- Proxmox API credentials and nodebuild.pkrvars.hcl- VM build user credentialsansible.pkrvars.hcl- Ansible automation usernetwork.pkrvars.hcl- Network settings (VLANs, subnets)linux-storage.pkrvars.hcl- Storage pools and partitions
Example: Proxmox Configuration
// config/proxmox.pkrvars.hcl
proxmox_api_token_id = "packer@pam!packer-token"
proxmox_api_token_secret = "<your-api-token-secret>"
proxmox_insecure_connection = true
proxmox_hostname = "proxmox.local"
proxmox_node = "pve-node-01"Example: Network Configuration
// config/network.pkrvars.hcl
vm_bridge_interface = "vmbr0"
vm_vlan_tag = "100"
// Optional: Static IP (defaults to DHCP if commented)
// vm_ip_address = "10.10.10.100"
// vm_ip_netmask = 24
// vm_ip_gateway = "10.10.10.1"
// vm_dns_list = ["10.10.10.10", "10.10.10.11"]# Linux/macOS/WSL
./docker-build.sh build
# Windows PowerShell
.\docker-build.ps1 buildSelect your desired OS from the interactive menu (e.g., option 15 for Ubuntu 24.04 LTS).
Validate all templates before building:
# Linux/macOS/WSL
./docker-build.sh validate
# Windows PowerShell
.\docker-build.ps1 validate- Proxmox VE 8.0 or later
- Docker 20.10+
- Docker Compose 2.0+
All dependencies (Packer, Ansible, plugins) are included in the Docker image.
Ubuntu - Install Packer & Ansible
# Add HashiCorp repository
sudo bash -c 'wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor > /usr/share/keyrings/hashicorp-archive-keyring.gpg'
sudo bash -c 'echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] \
https://apt.releases.hashicorp.com $(lsb_release -cs) main" > /etc/apt/sources.list.d/hashicorp.list'
# Install Packer
sudo apt update && sudo apt install packer
# Install Ansible
sudo apt install -y software-properties-common
sudo add-apt-repository --yes --update ppa:ansible/ansible
sudo apt install -y ansible-coreCentOS/RHEL - Install Packer & Ansible
# Install Packer
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
sudo yum -y install packer
# Install Ansible
sudo dnf -y install ansibleRequired Packer Plugins (auto-downloaded on first run):
- Packer Plugin for Git 0.6.2+
- Packer Plugin for Proxmox 1.2.1 (pinned)
oncall/
├── ansible/ # Ansible roles for OS configuration
├── builds/ # Packer templates per OS
├── config/ # Your specific settings (YOU EDIT THESE)
├── manifests/ # Build manifests (auto-generated)
└── tests/ # Validation test suites
Configuration templates are automatically created when you run ./docker-build.sh setup.
Optional: Create additional config directories for multiple environments:
./config.sh dev # Creates ./dev/ directory
./config.sh prod # Creates ./prod/ directoryThen customize the .pkrvars.hcl files for your infrastructure.
The NixOS template also has a local Packer+QEMU smoke test under tests/nixos-qemu.
This path is useful for iterating on the installer boot sequence and generated configuration.nix without needing Proxmox credentials.
Requirements:
packerqemu-system-x86_64qemu-img
Example:
cp tests/nixos-qemu/test.pkrvars.hcl.example tests/nixos-qemu/test.pkrvars.hcl
./tests/nixos-qemu/run.shThe QEMU smoke test currently defaults to a BIOS install path to keep local testing simple. The main Proxmox NixOS build remains available under builds/linux/nixos/25.11.
The config folder is the default folder. You can override the default by passing an alternate value as the first argument.
proxmox_api_token_id = "packer@pam!packer-token"
proxmox_api_token_secret = "<api-token-secret>"
proxmox_insecure_connection = true // false with valid TLS cert
proxmox_hostname = "proxmox.local"
proxmox_node = "pve-node-01"API Token Setup: Token must have PVEAdmin role. See Proxmox API documentation.
build_username = "admin"
build_password = "<plaintext-password>"
build_password_encrypted = "<sha512-hash>"
build_key = "<ssh-public-key>"Generate SHA-512 password hash:
mkpasswd -m sha512cryptGenerate SSH key:
ssh-keygen -t ecdsa -b 521 -C "automation"ansible_username = "ansible"
ansible_key = "<ansible-ssh-public-key>"For Ubuntu 26.04, configure each account's sudo policy in the OS-specific variable file:
// config/linux-ubuntu-26-04-lts.pkrvars.hcl
build_user_passwordless_sudo = true
ansible_user_passwordless_sudo = trueThese values are independent. The build-user setting controls the account
created by Subiquity and retained by Ansible. The Ansible-user setting controls
the dedicated automation account. Setting a value to false removes
NOPASSWD from that account's sudoers rule; it does not remove sudo access.
// Proxmox Network
vm_bridge_interface = "vmbr0"
vm_vlan_tag = "100"
// Optional: Static IP (comment out for DHCP)
// vm_ip_address = "10.10.10.100"
// vm_ip_netmask = 24
// vm_ip_gateway = "10.10.10.1"
// vm_dns_list = ["10.10.10.10", "10.10.10.11"]vm_storage_pool = "local-lvm"
vm_efi_storage_pool = "local-lvm"
vm_disk_device = "vda"
vm_disk_use_swap = trueStorage Layout: Simple Single Partition (UEFI)
vm_disk_partitions = [
{
name = "efi", size = 1024,
format = { label = "EFIFS", fstype = "fat32" },
mount = { path = "/boot/efi", options = "" },
volume_group = "",
},
{
name = "boot", size = 1024,
format = { label = "BOOTFS", fstype = "ext4" },
mount = { path = "/boot", options = "" },
volume_group = "",
},
{
name = "root", size = -1, // All remaining space
format = { label = "ROOTFS", fstype = "ext4" },
mount = { path = "/", options = "" },
volume_group = "",
},
]Storage Layout: LVM with CIS-Compliant Hardening
vm_disk_partitions = [
{ name = "efi", size = 1024, format = { label = "EFIFS", fstype = "fat32" },
mount = { path = "/boot/efi", options = "" }, volume_group = "" },
{ name = "boot", size = 1024, format = { label = "BOOTFS", fstype = "ext4" },
mount = { path = "/boot", options = "" }, volume_group = "" },
{ name = "sysvg", size = -1, format = { label = "", fstype = "" },
mount = { path = "", options = "" }, volume_group = "sysvg" },
]
vm_disk_lvm = [
{
name: "sysvg",
partitions: [
{ name = "lv_root", size = 10240, format = { label = "ROOTFS", fstype = "ext4" },
mount = { path = "/", options = "" }},
{ name = "lv_home", size = 4096, format = { label = "HOMEFS", fstype = "ext4" },
mount = { path = "/home", options = "nodev,nosuid" }},
{ name = "lv_tmp", size = 4096, format = { label = "TMPFS", fstype = "ext4" },
mount = { path = "/tmp", options = "nodev,noexec,nosuid" }},
{ name = "lv_var", size = 2048, format = { label = "VARFS", fstype = "ext4" },
mount = { path = "/var", options = "nodev" }},
{ name = "lv_var_log", size = 4096, format = { label = "VARLOGFS", fstype = "ext4" },
mount = { path = "/var/log", options = "nodev,noexec,nosuid" }},
],
}
]common_iso_storage = "local" // Proxmox storage with ISO files
common_data_source = "http" // or "disk" for isolated networks
common_http_port_min = 8000
common_http_port_max = 8099
common_ip_wait_timeout = "20m"
common_shutdown_timeout = "15m"Firewall Configuration (if using http data source):
# iptables
iptables -A INPUT -p tcp --match multiport --dports 8000:8099 -j ACCEPT
# firewalld
firewall-cmd --zone=public --add-port=8000-8099/tcp --permanent
firewall-cmd --reload./build.sh
# Or with Docker:
./docker-build.sh buildSelect from the menu:
- Options 1-14: Various Linux distributions
- Option 15: Ubuntu 24.04 LTS
- Option 21: Ubuntu 26.04 LTS
- Option 22: NixOS 25.11
- Options 18-20: Windows 11
./build.sh --debug
# Or with Docker:
docker-compose run --rm packer ./build.sh --debugEnables verbose output and pauses on errors.
- Initialize: Packer downloads required plugins (first run)
- Create VM: Temporary VM created in Proxmox
- Boot & Install: Automated OS installation from ISO
- Provision: Ansible applies configuration, updates, packages
- Convert: VM converted to reusable template
- Manifest: Build metadata saved to
manifests/
All templates include:
- QEMU Guest Agent
- SSH access (key-based)
- Ansible automation user
- Cloud-init support (Linux)
- Latest security patches
- Customizable storage layouts
# Development environment
./config.sh dev
./build.sh dev
# Production environment
./config.sh prod
./build.sh prodImportant: Docker containerized builds require a Linux host. Docker Desktop on Windows/macOS has networking limitations that prevent the Packer HTTP server from being accessible to Proxmox VMs during autoinstall.
./docker-build.sh setup # Initial setup (first time)
./docker-build.sh build # Run interactive build
./docker-build.sh validate # Validate all templates
./docker-build.sh shell # Open shell in container
./docker-build.sh clean # Remove Docker resources
./docker-build.sh rebuild # Rebuild image from scratch# Build image locally
docker-compose build
# Run interactively
docker-compose run --rm packer
# Run specific command
docker-compose run --rm packer ./validate.sh
# Debug mode
docker-compose run --rm packer ./build.sh --debugdocker-compose run --rm packer ./build.sh /workspace/my-custom-configEnable Packer logging:
PACKER_LOG=1 docker-compose run --rm packer ./build.shOr add to docker-compose.yml:
environment:
- PACKER_LOG=1
- PACKER_LOG_PATH=/workspace/packer.logBy default, the following are mounted:
./→/workspace(entire project)./config→/workspace/config(your configurations)./manifests→/workspace/manifests(build outputs)~/.ssh→/root/.ssh:ro(SSH keys, read-only)
Uses host network mode for Proxmox connectivity and HTTP server. Modify network_mode in docker-compose.yml if needed.
# Build image
docker build -t packer-proxmox:latest .
# Run interactively
docker run -it --rm --network host \
-v "$(pwd):/workspace" \
-v "$(pwd)/config:/workspace/config" \
-v "$(pwd)/manifests:/workspace/manifests" \
-v "$HOME/.ssh:/root/.ssh:ro" \
packer-proxmox:latest
# Run single command
docker run -it --rm --network host \
-v "$(pwd):/workspace" \
-v "$(pwd)/config:/workspace/config" \
packer-proxmox:latest \
./validate.sh- ✅ Consistent Environment - Same tools/versions across all systems
- ✅ Portability - Works on Windows, macOS, Linux without manual setup
- ✅ Isolation - No pollution of host system
- ✅ Easy Updates - Pull latest image or rebuild locally
- ✅ Team Collaboration - Everyone uses identical environment
Each OS has a config file: config/linux-<os>-<version>.pkrvars.hcl
Example config/linux-ubuntu-24-04-lts.pkrvars.hcl:
// Guest OS Settings
vm_os_language = "en_US"
vm_os_keyboard = "us"
vm_os_timezone = "America/Los_Angeles"
// Hardware
vm_cpu_count = 2
vm_cpu_sockets = 1
vm_cpu_type = "host" // "host" for best performance
vm_mem_size = 4096
vm_disk_size = "40G"
// Boot & Storage
vm_bios = "ovmf" // UEFI (or "seabios" for BIOS)
vm_disk_type = "virtio"
vm_disk_format = "raw"
// Cloud-Init
vm_cloudinit = true
// ISO
iso_path = "iso"
iso_file = "ubuntu-24.04-live-server-amd64.iso"
iso_checksum = "https://releases.ubuntu.com/noble/SHA256SUMS"Enable/disable per OS:
vm_cloudinit = true // Enable cloud-init
vm_cloudinit_disk_type = "ide" // or "scsi", "virtio"When cloning templates with cloud-init:
- Set hostname and network
- Inject SSH keys
- Run custom scripts
- Configure users and packages
Deploy VMs from templates using Terraform. See:
example_uefi_ubuntu_terraform/- Ubuntu deployment exampleterraform_import/- Import existing VMs
Problem: Build fails with config errors
Solution:
- Verify all required config files edited
- Check Proxmox credentials are correct
- Test connectivity:
ping proxmox.local
Problem: Build exceeds timeout
Solution: Increase timeout in OS-specific variables:
// In config/linux-ubuntu-24-04-lts.pkrvars.hcl
variable "timeout" {
default = "120m" // Increase from default 90m
}Problem: Packer can't find ISO file
Solution:
- Verify ISO uploaded to Proxmox storage
- Check
common_iso_storagematches storage name - Confirm
iso_pathandiso_fileare correct - Test: Navigate to Proxmox web UI → Storage → ISO images
Problem: Can't reach Proxmox or HTTP server timeouts
Solution:
- Ensure you're running on a Linux host (not Docker Desktop on Windows/macOS)
- Verify
vm_bridge_interfaceexists:pvesh get /nodes/<node>/network - Check VLAN tag matches network config
- Ensure firewall allows ports 8000-8099
- Verify
--network hostis used (default in docker-compose.yml) - Test:
curl http://<packer-host>:8000/
Note: Docker Desktop on Windows/macOS has network isolation that prevents --network host from working properly. For these platforms, install Packer and Ansible directly on the host.
Problem: Packer can't SSH to VM
Solution:
- Verify credentials in
config/build.pkrvars.hcl - Check VM has network connectivity (console in Proxmox)
- Verify QEMU guest agent starting:
systemctl status qemu-guest-agent - Check Proxmox firewall rules
Problem: Plugin download or compatibility issues
Solution: Re-initialize plugins:
cd builds/linux/ubuntu/24-04-lts/
packer init .Problem: File permission errors on mounted volumes
Solution:
sudo chown -R $(id -u):$(id -g) config/ manifests/Checklist:
- Container can reach Proxmox (check
proxmox_hostname) - Network mode is
hostin docker-compose.yml - Firewall allows HTTP ports 8000-8099
- No proxy blocking connections
Windows templates require:
- Valid product keys (not included)
- Correct OS image names in config
- VirtIO drivers for storage/network
See config/windows-desktop-11.pkrvars.hcl.example for configuration details.
Currently requires PVEAdmin role. Least-privilege configuration documentation in progress.
Pinned to v1.2.1 due to CPU bug in newer versions.
Based on:
- proxmox-packer-examples by ajschroeder
- packer-examples-for-vsphere by VMware
Technologies:
Infrastructure as Code
Network Automation & Infrastructure Engineering