Automating Virtual Machine (VM) template creation is essential for modern IT efficiency. HashiCorp Packer excels at this, allowing you to define and build identical machine images consistently. This guide offers a focused technical look at a Packer setup for creating an Ubuntu Server 22.04 template on Proxmox VE, with Docker pre-installed. We'll examine the HCL configurations that drive this automation.
Why Choose Packer for Building Your VM Templates?
The advantages are compelling:
Automation & Speed
Packer automates the entire image creation process, from OS installation to software provisioning and configuration, drastically reducing manual effort and speeding up template delivery.Consistency & Reliability
By defining your VM image as code, Packer ensures that every template is built exactly the same way, every time. This eliminates "it worked on my machine" scenarios and provides a reliable foundation for your deployments.Version Control (Infrastructure as Code)
Packer configurations are text files that can be versioned in systems like Git. This brings the benefits of Infrastructure as Code (IaC) to image management, allowing for peer reviews, change tracking, and easy rollbacks.Multi-Platform & Multi-Cloud
While this guide focuses on Proxmox, Packer supports numerous builders, enabling you to create identical images across various virtualization platforms and cloud providers from a single configuration.Extensibility & Integration
Packer seamlessly integrates with configuration management tools like Ansible, Chef, Puppet, and simple shell scripts for provisioning, allowing you to leverage existing automation scripts and expertise.
The Master Blueprint: ubuntu-22.pkr.hcl
This file contains the core logic for our image-building process.
packer {
required_plugins {
name = { // Typically 'proxmox'
version = "~> 1"
source = "github.com/hashicorp/proxmox"
}
}
}
source "proxmox-iso" "ubuntu-server-22-with-docker" {
proxmox_url = var.proxmox_api_url
insecure_skip_tls_verify = var.tls_verify
username = var.credentials["proxmox_api_token_id"]
token = var.credentials["proxmox_api_token_secret"]
iso_file = var.iso["path"]
iso_storage_pool = var.iso["storage_pool"]
unmount_iso = var.iso["unmount_enabled"]
node = var.node_name
template_description = var.template_description
vm_name = var.vm["name"]
cores = var.vm["cores"]
memory = var.vm["memory"]
sockets = var.vm["sockets"]
scsi_controller = var.vm["scsi_controller"]
qemu_agent = var.vm["qemu_agent_enabled"]
network_adapters {
model = var.network_adapters["model"]
bridge = var.network_adapters["bridge"]
firewall = var.network_adapters["firewall"]
}
disks {
disk_size = var.disks["disk_size"]
format = var.disks["format"]
storage_pool = var.disks["storage_pool"]
type = var.disks["type"]
}
http_directory = var.http_directory
ssh_username = var.ssh["username"]
ssh_timeout = var.ssh["timeout"]
ssh_private_key_file = var.ssh["private_key_file"]
boot_command = var.boot["boot_command"]
boot = var.boot["boot_order"]
boot_wait = var.boot["boot_wait"]
cloud_init = var.cloud_init["cloud_init_enabled"]
cloud_init_storage_pool = var.cloud_init["storage_pool"]
}
build {
name = "ubuntu-server-22"
sources = ["source.proxmox-iso.ubuntu-server-22-with-docker"]
provisioner "shell" {
inline = [
"while [ ! -f /var/lib/cloud/instance/boot-finished ]; do echo 'Waiting for cloud-init...'; sleep 1; done",
"sudo rm /etc/ssh/ssh_host_*",
"sudo truncate -s 0 /etc/machine-id",
"sudo apt -y autoremove --purge",
"sudo apt -y clean",
"sudo apt -y autoclean",
"sudo cloud-init clean",
"sudo rm -f /etc/cloud/cloud.cfg.d/subiquity-disable-cloudinit-networking.cfg",
"sudo rm -f /etc/netplan/00-installer-config.yaml",
"sudo sync"
]
}
provisioner "file" {
source = "files/99-pve.cfg"
destination = "/tmp/99-pve.cfg"
}
provisioner "shell" {
inline = ["sudo cp /tmp/99-pve.cfg /etc/cloud/cloud.cfg.d/99-pve.cfg"]
}
provisioner "shell" {
inline = [
"sudo apt-get update",
"sudo apt-get install -y ca-certificates curl gnupg lsb-release",
"curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg",
"echo \"deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable\" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null",
"sudo apt-get -y update",
"sudo apt-get install -y docker-ce docker-ce-cli containerd.io"
]
}
}
The Definitions: variables.pkr.hcl
This file declares the variables used in the main Packer configuration, specifying their expected types.
variable "proxmox_api_url" {
type = string
}
variable "credentials" {
type = map(string)
}
variable "tls_verify" {
type = bool
}
variable "iso" {
type = map(string)
}
variable "node_name" {
type = string
}
variable "template_description" {
type = string
}
variable "vm" {
type = object({
name = string
cores = number
memory = number
sockets = number
scsi_controller = string
qemu_agent_enabled = bool
})
}
variable "network_adapters" {
type = map(string)
}
variable "disks" {
type = map(string)
}
variable "http_directory" {
type = string
}
variable "ssh" {
type = map(string)
}
variable "boot" {
type = object({
boot_command = list(string)
boot_order = string
boot_wait = string
})
}
variable "cloud_init" {
type = object({
cloud_init_enabled = bool
storage_pool = string
})
}
Using this file clearly defines all expected inputs (like string, bool, map, object), making the main configuration cleaner, more reusable, and secure by separating definitions from sensitive values.
Your Environment's Details: The *.auto.pkrvars.hcl File
Packer automatically loads files ending in .auto.pkrvars.hcl to populate the declared variables. This file contains your specific values and should be kept private (e.g., via .gitignore
)
// Example: my-proxmox-values.auto.pkrvars.hcl
proxmox_api_url = "https://your-proxmox-ip-or-hostname:8006/api2/json"
credentials = {
proxmox_api_token_id = "your_user@pam!your_token_id"
proxmox_api_token_secret = "your_super_secret_token_value"
}
tls_verify = false // Or true, for production with valid SSL certificates
iso = {
path = "local:iso/ubuntu-22.04.4-live-server-amd64.iso" // Adjust to your ISO path
storage_pool = "local" // Your Proxmox ISO storage
unmount_enabled = true
}
node_name = "pve" // Your Proxmox node name
template_description = "Ubuntu Server 22.04.4 LTS with Docker (Packer)"
vm = {
name = "packer-ubuntu-22-docker"
cores = 2
memory = 4096 // 4GB
sockets = 1
scsi_controller = "virtio-scsi-single"
qemu_agent_enabled = true
}
network_adapters = {
model = "virtio"
bridge = "vmbr0" // Your Proxmox bridge
firewall = "false"
}
disks = {
disk_size = "32G"
format = "qcow2"
storage_pool = "local-lvm" // Your Proxmox VM disk storage
type = "scsi"
}
http_directory = "http" // Contains user-data/meta-data for autoinstall
ssh = {
username = "packeruser" // User created by autoinstall
timeout = "30m"
private_key_file = "~/.ssh/id_rsa_packer" // Key for SSH access
}
boot = {
boot_command = [ // Tailor to your specific ISO's autoinstall prompts
"<esc><wait><esc><wait><f10><wait><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
" vmlinuz initrd=initrd.gz --- autoinstall ds=nocloud-net\\;s=http://{{ .HTTPIP }}:{{ .HTTPPort }}/",
"c<wait>"
]
boot_order = "cdn"
boot_wait = "10s"
}
cloud_init = {
cloud_init_enabled = true
storage_pool = "local-lvm" // Storage for Cloud-Init drive
}
This file injects your specific environmental details—API credentials, node names, storage pools, and desired VM specifications—into the Packer build process.
Key Advantages of This Packer Workflow
- Reproducibility: Guarantees identical templates every build.
- Efficiency: Significantly reduces manual effort and time.
- Versioning (IaC): Enables tracking changes and collaboration via version control.
- Consistency: Ensures a standard base for all deployed VMs.
- Scalability: Simplifies updating or creating template variations.
Conclusion
This Packer configuration provides a robust and automated method for creating Docker-equipped Ubuntu 22.04 templates on Proxmox. By structuring your configurations clearly, you achieve a maintainable and highly effective workflow, reaping the rewards of automation and consistency.