Proxmox Images with Packer and Ubuntu 24.04

jtorrex

2026/02/07

Categories: devops blog Tags: blog proxmox terraform devops iac

Introduction

In this blog post, we will explore how to automate the creation of Proxmox images using Packer and Ubuntu 24.04. This process allows us to quickly and efficiently generate customized Proxmox templates that can be used for various purposes, such as testing, development, or production environments.

Prerequisites

The following prerequisites are required to follow along with this guide:

Code Structure

The directory layout for this project is organized as follows, making use of a modular approach to separate concerns and improve maintainability:

packer
└── linux
    └── ubuntu
        └── 24-04
            ├── cidata
               ├── meta-data
               └── user-data
            ├── provider.pkr.hcl
            ├── ubuntu.pkr.hcl
            ├── variables.pkr.hcl
            └── .env

On the directory we have the following files:

Setting Up the Proxmox Provider

In the provider.pkr.hcl file, we define the Proxmox provider configuration for Packer. This includes specifying the connection details to the Proxmox API, such as the API URL, username, and password. By using environment variables to store sensitive information, we can ensure that our credentials are not hardcoded in the configuration files, enhancing security.

packer {
  required_version = ">= 1.10.3"
  required_plugins {
    proxmox = {
      version = "v1.2.3"
      source  = "github.com/hashicorp/proxmox"
    }
  }
}

Setting up variables

In the variables.pkr.hcl file, we define the variables that will be used in our Packer configuration. This allows us to easily customize the build process by simply changing the values of these variables without modifying the main configuration files.

variable "proxmox_api_url" {
  type      = string
  sensitive = true
}

variable "proxmox_username" {
  type      = string
  sensitive = true
}

variable "proxmox_password" {
  type      = string
  sensitive = true
}

variable "insecure_skip_tls_verify" {
  type    = bool
  default = false
}

variable "ssh_authorized_key" {
  type      = string
  sensitive = true
}

variable "ssh_password" {
  type      = string
  sensitive = true
}

variable "vm_name" {
  type        = string
  description = "Name of the Proxmox VM to create"
  default     = "ubuntu-24.04-template"
}

variable "vm_template_name" {
  type        = string
  description = "Name of the Proxmox VM template to use for provisioning"
  default     = "ubuntu-24.04-template"
}

variable "iso_storage_pool" {
  type        = string
  description = "Proxmox storage pool where the ISO file is located"
  default     = "local"
}

variable "iso_file" {
  type        = string
  description = "Path to the ISO file for VM installation"
  default     = "local:iso/ubuntu-24.04.3-live-server-amd64.iso"
}

variable "network_bridge" {
  type        = string
  description = "Proxmox network bridge to attach VMs to"
  default     = "vmbr0"

}

variable "storage_pool" {
  type        = string
  description = "Proxmox storage pool for VM disks"
  default     = "local-zfs"
}

variable "cloudinit_storage_pool" {
  type        = string
  description = "Proxmox storage pool for cloud-init disks"
  default     = "local"
}

variable "cloudinit_iso_files_dir" {
  type        = string
  description = "Directory containing cloud-init ISO files to attach to VMs"
}

variable "ssh_host" {
  type        = string
  description = "SSH host for provisioning"
  default     = "localhost"
}

Passing Credentials Securely

To securely pass credentials to Packer, we utilize environment variables. In the .env file, we define the necessary environment variables for Proxmox API access, such as PROXMOX_API_URL, PROXMOX_USERNAME, and PROXMOX_PASSWORD. By sourcing this file before running the Packer build, we ensure that the credentials are available in the environment without exposing them in the configuration files.

#!/bin/bash
export PKR_VAR_proxmox_api_url="https://10.3.0.1:8006/api2/json"
export PKR_VAR_proxmox_username="root@pam"
export PKR_VAR_proxmox_password="your-secure-password"
export PKR_VAR_ssh_authorized_key="ssh-ed25519..."
export PKR_VAR_ssh_password="ubuntu"
export PKR_VAR_ssh_private_key_file="~/.ssh/provisioning-homelab"
export PKR_VAR_vm_template_name="ubuntu-24.04-template"
export PKR_VAR_vm_name="ubuntu-24.04-template"
export PKR_VAR_iso_storage_pool="local"
export PKR_VAR_iso_file="local:iso/ubuntu-24.04-live-server-amd64.iso"
export PKR_VAR_network_bridge="vmbr1"
export PKR_VAR_cloudinit_storage_pool="local"
export PKR_VAR_ssh_host="10.3.1.250"
export PKR_VAR_cloudinit_iso_files_dir="./cidata/*"

Defining the Packer Build Configuration

In the ubuntu.pkr.hcl file, we define the Packer build configuration for creating the Proxmox image. This includes specifying the source ISO file, VM settings such as CPU, memory, and disk size, and the provisioning steps to automate the setup of the VM using cloud-init.

# Ubuntu Server 24.04
source "proxmox-iso" "ubuntu-server-noble" {

    # Proxmox Connection Settings
    proxmox_url = var.proxmox_api_url
    insecure_skip_tls_verify = true
    username = var.proxmox_username
    password = var.proxmox_password
    node = "pve"
    task_timeout = "10m"
 
    # VM General Settings
    vm_id = "9001"
    vm_name = var.vm_name
    template_name = var.vm_template_name
    template_description = "Ubuntu Server 2404 Image"

    boot_iso {
      iso_file       = var.iso_file
      iso_storage_pool = var.iso_storage_pool
      unmount        = true
    }
 
    cpu_type = "host"
    sockets = "1"
    cores = "4"
    memory = "4096"
 
    # VM Network Settings
    network_adapters {
      bridge = var.network_bridge
      model = "virtio"
      firewall = "false"
    }

    disks {
      type = "virtio"
      disk_size = "20G"
      storage_pool = var.storage_pool
      format = "raw"
    }
 
    os = "l26"
    scsi_controller = "virtio-scsi-pci"
    onboot = true
    qemu_agent = true
    bios = "seabios"

    additional_iso_files {
      cd_files = [var.cloudinit_iso_files_dir]
      cd_label = "cidata"
      iso_storage_pool = "local"
      unmount = true
    }

    cloud_init = true
    cloud_init_storage_pool = var.cloudinit_storage_pool

    # Explicitly set boot device and timeout
    boot = "c"
    boot_wait = "5s"
    boot_command = [
      "<esc><wait>c",
      "linux /casper/vmlinuz --- autoinstall ds=\"nocloud\"",
      "<enter><wait5s>",
      "initrd /casper/initrd",
      "<enter><wait5s>",
      "boot",
      "<enter>"
    ]

    http_directory = "./cidata/"
    communicator = "ssh"
    ssh_host = var.ssh_host
    ssh_username = "ubuntu"
    ssh_password = var.ssh_password
    ssh_private_key_file = "~/.ssh/provisioning-homelab"
    ssh_timeout = "20m"
}

# Build Definition to create the VM Template
build {

    name = "ubuntu-server-2404-template-build"
    sources = ["source.proxmox-iso.ubuntu-server-noble"]

    provisioner "shell" {
        inline = [
            "while [ ! -f /var/lib/cloud/instance/boot-finished ]; do echo 'Waiting for cloud-init...'; sleep 1; done",
            "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 sync"
        ]
    }
}

Defining Cloud-Init configuration

The meta-data and user-data files contain the cloud-init configuration for the Proxmox image. This allows us to automate the initial setup of the VM, such as configuring the hostname, setting up users, and installing necessary packages.

instance-id: ubuntu-server-2404-template
hostname: ubuntu-server-2404-template
#cloud-config
autoinstall:
  interactive-sessions: []
  version: 1
  locale: en_US
  keyboard:
    layout: us
  ssh:
    install-server: true
    allow-pw: true
    authorized_keys:
     - "ssh-ed25519 ... provisioning-homelab"
  storage:
    layout:
      name: direct
    swap:
      size: 0
  network:
    version: 2
    ethernets:
      ens18:
        dhcp4: false
        addresses:
          - 10.3.1.250/24
        gateway4: 10.3.0.1
        nameservers:
          addresses:
            - 1.1.1.1
            - 8.8.8.8
  packages:
    - qemu-guest-agent
    - cloud-init
  user-data:
    package_upgrade: true
    timezone: Europe/Madrid
    users:
      - name: ubuntu
        passwd: "YOUR-SECURE-PASSWORD"
        groups: [adm, sudo]
        lock-passwd: false
        sudo: ALL=(ALL) NOPASSWD:ALL
        shell: /bin/bash

The password for the ubuntu user should be set to a hashed value for security reasons. You can generate a hashed password using the mkpasswd command from the whois package:

~ ❯ mkpasswd --method=SHA-512 --rounds=4096
Password:
$6$rounds=4096$1O6VYVvmNjSMmkE2$j7zzMzvx2bFZzJlbYloUkeqyFdN9BtBZTRO4WYJk/GaApiJ7a3aqvJFqA6.2o4TD1WJgk4LkuSVOgLTI51BFk.

That credentials will be used for the initial setup of the VM, allowing us to log in and perform any necessary configurations after the image is created.

Running the Packer Build

When we have all the configuration files set up, we can run the Packer build process. First, we need to source the .env file to load the environment variables, and then we can execute the build.sh script to start the build process.

source .env
packer init .
packer validate .
packer build .

The output of the build process will show the steps being executed, including creating the CD cloudinit disk, starting the VM, provisioning it with cloud-init, and finally converting it to a template. Once the build is complete, we will have a Proxmox template ready to be used for creating new VMs.

linux/ubuntu/24-04 ❯ source ./cidata/pve-rfl/.env                                                took 5s
linux/ubuntu/24-04 ❯ ./build.sh
The configuration is valid.
<sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: output will be in this color.

==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: Creating CD disk...
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: xorriso 1.5.6 : RockRidge filesystem manipulator, libburnia project.
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: Drive current: -outdev 'stdio:/tmp/packer545272301.iso'
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: Media current: stdio file, overwriteable
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: Media status : is blank
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: Media summary: 0 sessions, 0 data blocks, 0 data, 15.4g free
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: xorriso : WARNING : -volid text does not comply to ISO 9660 / ECMA 119 rules
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: Added to ISO image: directory '/'='/tmp/packer_to_cdrom2129445467'
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: xorriso : UPDATE :       3 files added in 1 seconds
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: xorriso : UPDATE :       3 files added in 1 seconds
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: xorriso : UPDATE :  0.00% done
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: ISO image produced: 186 sectors
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: Written to medium : 186 sectors at LBA 0
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: Writing to 'stdio:/tmp/packer545272301.iso' completed successfully.
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: Done copying paths from CD_dirs
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: Uploaded ISO to local:iso/packer545272301.iso
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: Creating VM
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: Starting VM
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: Starting HTTP server on port 8949
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: Waiting 5s for boot
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: Typing the boot command
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: Using SSH communicator to connect: 10.3.1.250
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: Waiting for SSH to become available...
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: Connected to SSH!
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: Provisioning with shell script: /tmp/packer-shell3249938312
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: Waiting for cloud-init...
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: Waiting for cloud-init...
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: Waiting for cloud-init...
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: Waiting for cloud-init...
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble:
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: WARNING: apt does not have a stable CLI interface. Use with caution in scripts.
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble:
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: Reading package lists...
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: Building dependency tree...
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: Reading state information...
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble:
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: WARNING: apt does not have a stable CLI interface. Use with caution in scripts.
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble:
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: WARNING: apt does not have a stable CLI interface. Use with caution in scripts.
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble:
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: Reading package lists...
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: Building dependency tree...
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: Reading state information...
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: Stopping VM
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: Converting VM to template
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: Adding a cloud-init cdrom in storage pool local
==> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: Deleted generated ISO from local:iso/packer545272301.iso
Build '<sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble' finished after 20 minutes 9 seconds.

==> Wait completed after 20 minutes 9 seconds

==> Builds finished. The artifacts of successful builds are:
--> <sensitive>-server-2404-template-build.proxmox-iso.<sensitive>-server-noble: A template was created: 9001

Once the build process is complete, we will have a Proxmox template named ubuntu-24.04-template that can be used to create new VMs with the same configuration and setup as defined in our Packer configuration.

You can check it from the Proxmox web interface or using the Proxmox CLI:

root@pve:~# qm list
      VMID NAME                 STATUS     MEM(MB)    BOOTDISK(GB) PID
      ...
      9001 ubuntu-24.04-template stopped    2048               0.00 0
      ...