Skip to content

Repository files navigation

Packer Templates for Proxmox

Automated VM template creation for Proxmox using HashiCorp Packer and Ansible.


Table of Contents


Introduction

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.

Key Features

  • 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

Supported Operating Systems

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

Account credential guidance

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

Quick Start

Prerequisites

  • 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

3-Step Setup

1. Clone and Initialize

# Linux (required for Docker networking)
git clone https://github.com/traefikturkey/oncall.git
cd oncall
./docker-build.sh setup

This builds the Docker image and creates configuration template files in ./config/.

2. Configure for Your Environment

Edit configuration files in the config/ directory:

Essential configuration files:

  • proxmox.pkrvars.hcl - Proxmox API credentials and node
  • build.pkrvars.hcl - VM build user credentials
  • ansible.pkrvars.hcl - Ansible automation user
  • network.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"]

3. Build a Template

# Linux/macOS/WSL
./docker-build.sh build

# Windows PowerShell
.\docker-build.ps1 build

Select your desired OS from the interactive menu (e.g., option 15 for Ubuntu 24.04 LTS).

Validation

Validate all templates before building:

# Linux/macOS/WSL
./docker-build.sh validate

# Windows PowerShell
.\docker-build.ps1 validate

Requirements

Infrastructure Platform

  • Proxmox VE 8.0 or later

Docker Environment (Recommended)

  • Docker 20.10+
  • Docker Compose 2.0+

All dependencies (Packer, Ansible, plugins) are included in the Docker image.

Manual Installation (Alternative)

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-core
CentOS/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 ansible

Required Packer Plugins (auto-downloaded on first run):


Configuration Guide

Directory Structure

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 Scripts

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

Then customize the .pkrvars.hcl files for your infrastructure.

Local NixOS QEMU Smoke Test

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:

  • packer
  • qemu-system-x86_64
  • qemu-img

Example:

cp tests/nixos-qemu/test.pkrvars.hcl.example tests/nixos-qemu/test.pkrvars.hcl
./tests/nixos-qemu/run.sh

The 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.

Essential Configuration Files

1. Proxmox Connection (config/proxmox.pkrvars.hcl)

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.

2. Build User Credentials (config/build.pkrvars.hcl)

build_username           = "admin"
build_password           = "<plaintext-password>"
build_password_encrypted = "<sha512-hash>"
build_key                = "<ssh-public-key>"

Generate SHA-512 password hash:

mkpasswd -m sha512crypt

Generate SSH key:

ssh-keygen -t ecdsa -b 521 -C "automation"

3. Ansible User (config/ansible.pkrvars.hcl)

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 = true

These 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.

4. Network Settings (config/network.pkrvars.hcl)

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

5. Storage Configuration (config/linux-storage.pkrvars.hcl)

vm_storage_pool     = "local-lvm"
vm_efi_storage_pool = "local-lvm"
vm_disk_device      = "vda"
vm_disk_use_swap    = true
Storage 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" }},
    ],
  }
]

6. Common Settings (config/common.pkrvars.hcl)

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

Building VM Templates

Interactive Build

./build.sh
# Or with Docker:
./docker-build.sh build

Select 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

Debug Mode

./build.sh --debug
# Or with Docker:
docker-compose run --rm packer ./build.sh --debug

Enables verbose output and pauses on errors.

Build Process

  1. Initialize: Packer downloads required plugins (first run)
  2. Create VM: Temporary VM created in Proxmox
  3. Boot & Install: Automated OS installation from ISO
  4. Provision: Ansible applies configuration, updates, packages
  5. Convert: VM converted to reusable template
  6. Manifest: Build metadata saved to manifests/

Template Features

All templates include:

  • QEMU Guest Agent
  • SSH access (key-based)
  • Ansible automation user
  • Cloud-init support (Linux)
  • Latest security patches
  • Customizable storage layouts

Multiple Environments

# Development environment
./config.sh dev
./build.sh dev

# Production environment
./config.sh prod
./build.sh prod

Docker Usage

Important: 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.

Quick Commands (Linux Only)

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

Using Docker Compose Directly

# 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 --debug

Custom Config Path

docker-compose run --rm packer ./build.sh /workspace/my-custom-config

Environment Variables

Enable Packer logging:

PACKER_LOG=1 docker-compose run --rm packer ./build.sh

Or add to docker-compose.yml:

environment:
  - PACKER_LOG=1
  - PACKER_LOG_PATH=/workspace/packer.log

Volume Mounts

By 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)

Network Mode

Uses host network mode for Proxmox connectivity and HTTP server. Modify network_mode in docker-compose.yml if needed.

Building Without Docker Compose

# 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

Docker Benefits

  • 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

Advanced Configuration

Per-OS Template Settings

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"

Cloud-Init Support

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

Terraform Integration

Deploy VMs from templates using Terraform. See:

  • example_uefi_ubuntu_terraform/ - Ubuntu deployment example
  • terraform_import/ - Import existing VMs

Troubleshooting

Configuration Issues

Problem: Build fails with config errors

Solution:

  1. Verify all required config files edited
  2. Check Proxmox credentials are correct
  3. Test connectivity: ping proxmox.local

Build Timeouts

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
}

ISO Not Found

Problem: Packer can't find ISO file

Solution:

  1. Verify ISO uploaded to Proxmox storage
  2. Check common_iso_storage matches storage name
  3. Confirm iso_path and iso_file are correct
  4. Test: Navigate to Proxmox web UI → Storage → ISO images

Network Connectivity

Problem: Can't reach Proxmox or HTTP server timeouts

Solution:

  1. Ensure you're running on a Linux host (not Docker Desktop on Windows/macOS)
  2. Verify vm_bridge_interface exists: pvesh get /nodes/<node>/network
  3. Check VLAN tag matches network config
  4. Ensure firewall allows ports 8000-8099
  5. Verify --network host is used (default in docker-compose.yml)
  6. 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.

SSH Connection Failures

Problem: Packer can't SSH to VM

Solution:

  1. Verify credentials in config/build.pkrvars.hcl
  2. Check VM has network connectivity (console in Proxmox)
  3. Verify QEMU guest agent starting: systemctl status qemu-guest-agent
  4. Check Proxmox firewall rules

Packer Plugin Errors

Problem: Plugin download or compatibility issues

Solution: Re-initialize plugins:

cd builds/linux/ubuntu/24-04-lts/
packer init .

Permission Issues (Docker)

Problem: File permission errors on mounted volumes

Solution:

sudo chown -R $(id -u):$(id -g) config/ manifests/

Proxmox Connection Issues (Docker)

Checklist:

  • Container can reach Proxmox (check proxmox_hostname)
  • Network mode is host in docker-compose.yml
  • Firewall allows HTTP ports 8000-8099
  • No proxy blocking connections

Known Issues

Windows Builds

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.

Proxmox API Permissions

Currently requires PVEAdmin role. Least-privilege configuration documentation in progress.

Packer Proxmox Plugin

Pinned to v1.2.1 due to CPU bug in newer versions.


Credits

Based on:

Technologies:


Infrastructure as Code
Network Automation & Infrastructure Engineering

Releases

Packages

Contributors

Languages