Skip to content

What a golden boy – Use Packer to build Proxmox images

We use Hashicorps Packer to build virtual machine templates within Proxmox. They can then easily be used to create brand new virtual machines from.

Let’s have some words regarding Packer before we jump in. In general, Packer can be used to provide customized virtual machine images to several providers. This may be a Hyperscaler like AWS or a Hypervisor running your home lab like Proxmox. The big advantage using Packer over maintaining your VM images manually is, you can create new images all the time automatically as you need them. Let’s say, you want to have always a special kind of software installed on all your VMs out of the box. You could do so by creating a basic VM which you can clone then and then running maybe an Ansible playbook against it, that does the deployment of the software on this new VM. The downside with this approach is, that you hit errors quite late in the process and fixing them might be time consuming.

Another benefit, especially in Proxmox is, that you cannot “start” VM templates once you created them. So adding another software to this template or updating an existing software is also a time consuming process. Which can all be done by Packer automatically.

Installation of Packer

With Ubuntu 22.04, an older packer release is part of the default repositories and can easily installed using apt. For older or other Linux / MacOS / Windows releases, read the official documentation here. Cause we want to have a more recent release of packer, we add the Hashicorp repository anyway, this has to be done differently than described in the documentation starting with Ubuntu 22.04 (cause of the deprecation of apt-key).

sudo curl | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
sudo echo "deb [arch=amd64 signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] $(lsb_release -cs) main" > /etc/apt/sources.list.d/archive_uri-https_apt_releases_hashicorp_com-jammy.list

This will add the repositories public key and the Hashicorp repo to our source list for apt. Now you can install a recent release of packer using apt.

sudo apt update && sudo apt install packer

Start using Packer

Creating images with packer involves three major components. In this tutorial, we will leave out the Post-Processor, but I will explain what it can do too:

  • Builder: Builders create machines and generate images from those machines for various platforms. Packer also has some builders that perform helper tasks, like running provisioners. There is a long list of builder plugins, here we use Proxmox, but it can also be Virtualbox, AWS and so on.
  • Provisioner: Provisioners use built-in and third-party software to install and configure the machine image after booting. So they can be scripts to install certain software, create users…
  • Post-processor: Post-processors run after builders and provisioners. Post-processors are optional, and you can use them to upload artifacts, re-package files, and more.

Let’s get started with the initial configuration of Packer. You can find the most recent examples in my Github repository. Let’s create a configuration file config.pkr.hcl. This tells packer during initialization, which plugins need to be installed (in our case the Proxmox plugin).

packer {
  required_plugins {
    proxmox = {
      version = ">= 1.0.6"
      source  = ""

Run packer init config.pkr.hcl to get packer initialized and all needed plugins installed.

Further, we create a var-file called variables.json, which will contain general configuration (like how to connect to your Proxmox server). We will reuse the configured variables soon in the actual template definition. A complete list of possible parameters can be found here. You may specify some additional variables which you can reference later during building too (e.g. a version tag, usernames…).

  "proxmox_hostname": "proxmox1:8006",
  "proxmox_username": "root@pam",
  "proxmox_password": "supersecret",
  "proxmox_node":     "proxmox1",
  "version_tag":      "1.0.0",
  "clone_vm":         "vmSrvUbuntu22tmpl",

  "ssh_username":     "ubuntu",
  "ssh_password":     "packer"

Be aware, that on the Proxmox VM template we use as source for a new template (in this case vmSrvUbuntu22tmpl) needs a SSH username with a given password. Packer will connect after the initial clone to the VM to do it’s provisioning. I use the version_tag for getting a little order in created templates. So you may bump to a new version_tag in order to identify your VM templates easier later on.

Now we get to the third and final file ready, this time we descripe the actual building and provisioning process of packer. Create a file called ubuntu2204.json with the following example content:

  "builders": [
      "type": "proxmox-clone",
      "proxmox_url": "https://{{user `proxmox_hostname`}}/api2/json",
      "username": "{{user `proxmox_username`}}",
      "password": "{{user `proxmox_password`}}",
      "node": "{{user `proxmox_node`}}",
      "insecure_skip_tls_verify": true,
      "task_timeout": "10m",
      "clone_vm": "{{ user `clone_vm`}}",
      "full_clone": false,

      "template_name": "{{ user `clone_vm` }}{{ user `version_tag`}}",
      "template_description": "image made from packer",

      "os": "l26",
      "cores": 1,
      "sockets": 1,
      "memory": 2048,
      "network_adapters": [
          "bridge": "vmbr0"

			"ssh_timeout": "90m",
			"ssh_password": "{{ user `ssh_password` }}",
			"ssh_username": "{{ user `ssh_username` }}"

	"provisioners": [
			"pause_before": "20s",
			"type": "shell",
			"inline": [
				"echo {{ user `version_tag` }} > provision.txt",
				"sudo apt update",
				"sudo apt -y upgrade",
				"sudo apt -y install apache2",
				"sudo apt -y install qemu-guest-agent cloud-init"

As you can see, we refer to the variables we defined in the variables.json file earlier. This is done in the format {{ user ... }}, telling packer, that a user defined variable follows. You can also refer to environment variables by using env instead of user. For the sake of this demo, I use a linked clone instead of a full clone, if you want to have a full clone created, delete the line with full_clone: false in it, cause true is the default value.

Following the builders section, there comes the provisioners section. I use the shell provisioner with some inline scripting in it. Mainly I do a full package upgrade on the temporary created VM, followed by installing apache2, qemu-guest-agent and cloud-init. Time to build our template. Don’t forgett to reference the variable.json file by the -var-file option:

❯ packer build -var-file=variables.json ubuntu2204.json
proxmox-clone: output will be in this color.

==> proxmox-clone: Creating VM
==> proxmox-clone: No VM ID given, getting next free from Proxmox
==> proxmox-clone: Starting VM
==> proxmox-clone: Waiting for SSH to become available...
==> proxmox-clone: Connected to SSH!
==> proxmox-clone: Pausing 20s before the next provisioner...
==> proxmox-clone: Provisioning with shell script: /tmp/packer-shell3657004111
==> proxmox-clone:
==> proxmox-clone: WARNING: apt does not have a stable CLI interface. Use with caution in scripts.
==> proxmox-clone:
    proxmox-clone: Get:1 jammy-security InRelease [110 kB]
    proxmox-clone: Get:2 jammy-security/main i386 Packages [139 kB]
    proxmox-clone: Hit:3 jammy InRelease
    proxmox-clone: Get:4 jammy-security/main amd64 Packages [372 kB]
    proxmox-clone: Get:5 jammy-updates InRelease [114 kB]
==> proxmox-clone: Stopping VM
==> proxmox-clone: Converting VM to template
Build 'proxmox-clone' finished after 1 minute 17 seconds.

==> Wait completed after 1 minute 17 seconds

==> Builds finished. The artifacts of successful builds are:
--> proxmox-clone: A template was created: 107

That was real fast! When you check your Proxmox WebUI, you should be able to see a new VM template (in my case with the VM ID 107). You can now create a new VM out of this template with all the needed provisioning you may want.


Leave a Reply

Your email address will not be published. Required fields are marked *