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
- 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>"// 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