Skip to content

How to build a golden VM image with Packer

How-to · Updated Jun 2026

Coming from another cloud?

▸AWS·EC2 Image Builder

This Quake AI feature maps to AWS’s EC2 Image Builder.

▸Azure·VM Image Builder

This Quake AI feature maps to Azure’s VM Image Builder.

Before this

How to build a golden VM image with Packer

Build a reusable golden VM image with Packer and its OpenStack builder, then publish the result to the Quake AI Images service (OpenStack Glance) so you can launch identical instances from it.

Prerequisites#

  • A Quake AI account with a project
  • An application credential for the build, with the roles Packer needs to create instances, volumes, floating IPs, and images
  • The OpenStack CLI installed and authenticated, to verify the build inputs and the published image
  • A base image already in the Images service to build on (a stock Linux cloud image with cloud-init, for example Ubuntu-24.04)
  • A private network and subnet in your project for the build instance, plus access to the external network for SSH floating IP allocation
  • Packer 1.9 or later on the machine that runs the build

Install Packer and the OpenStack plugin#

Install Packer with your package manager or from the Packer downloads page, then confirm the version:

bash
packer version

Packer manages builder plugins separately from the core binary. Declare the OpenStack plugin in a required_plugins block (shown in the template below) and install it:

bash
packer plugins install github.com/hashicorp/openstack

Authenticate the build with an application credential#

The OpenStack builder reads the same environment variables as the OpenStack CLI. Authenticate with an application credential so the build never carries your account password. Export the credential the application credentials guide produced:

bash
export OS_AUTH_TYPE=v3applicationcredential
export OS_AUTH_URL=https://YOUR_REGION_AUTH_ENDPOINT/v3
export OS_APPLICATION_CREDENTIAL_ID=YOUR_APP_CREDENTIAL_ID
export OS_APPLICATION_CREDENTIAL_SECRET=YOUR_APP_CREDENTIAL_SECRET
export OS_REGION_NAME=YOUR_REGION

Confirm the credential resolves before you build:

bash
openstack image list

The command lists the images in your project, including the base image you build on.

Allow SSH through a security group#

The default security group allows inbound traffic only from other members of the same group. Packer runs on your workstation and connects to the build instance over SSH through the floating IP, so the build VM sits outside that trust boundary. Create a dedicated security group with an SSH ingress rule and reference it in the Packer source block.

Replace YOUR_BUILD_HOST_CIDR with the public address of the machine running Packer, for example 203.0.113.10/32. Narrow the CIDR in production; use a wider range only when your build host has a dynamic address and you accept the exposure.

bash
openstack security group create packer-build-ssh
openstack security group rule create \
  --protocol tcp --dst-port 22 --remote-ip YOUR_BUILD_HOST_CIDR \
  packer-build-ssh

For protocol presets, remote security group references, and Console steps, see How to create security group rules.

Write the Packer template#

Save the following HCL2 template as golden-image.pkr.hcl. It boots a base image, runs a shell provisioner, and publishes the result to the Images service.

HCL
packer {
  required_plugins {
    openstack = {
      version = ">= 1.1.0"
      source  = "github.com/hashicorp/openstack"
    }
  }
}

locals {
  timestamp = formatdate("YYYYMMDD-hhmm", timestamp())
}

source "openstack" "golden" {
  source_image_name       = "Ubuntu-24.04"
  flavor                  = "s1a.small"
  ssh_username            = "ubuntu"
  networks                = ["YOUR_PRIVATE_NETWORK_ID"]
  floating_ip_network     = "PublicStatic"
  security_groups         = ["packer-build-ssh"]
  use_blockstorage_volume = true
  volume_size             = 20
  image_name              = "golden-ubuntu-2404-${local.timestamp}"
  image_disk_format       = "qcow2"
}

build {
  sources = ["source.openstack.golden"]

  provisioner "shell" {
    inline = [
      "sudo cloud-init status --wait",
      "sudo apt-get update",
      "sudo apt-get upgrade -y",
      "sudo apt-get install -y qemu-guest-agent",
      "sudo systemctl enable qemu-guest-agent",
    ]
  }
}

Boot the build instance from a volume#

Quake AI flavors report Disk: 0 and Ephemeral: 0, so every instance boots from a Block Storage (OpenStack Cinder) volume rather than a flavor-local disk. The Packer build instance follows the same rule. Set use_blockstorage_volume = true and volume_size in the source block so Packer creates a volume-backed build instance. Without these settings the build fails, because the flavor provides no root disk. Size volume_size for the base image plus the space your provisioners add; the default derives from the source image and is often too small once provisioners run. The reasoning behind disk=0 flavors lives in the flavors CLI reference.

Floating IP for the build#

When Packer runs outside the project network (a workstation or an external CI runner), it reaches the build instance over SSH through a floating IP. Place the build instance on a private network in networks (pass the network UUID from openstack network list), and set floating_ip_network to your project's external network (for example PublicStatic) so Packer allocates exactly one floating IP for SSH and releases it when the build finishes. Attaching the build VM directly to the external network fails network allocation on volume-backed instances.

If Packer runs on an instance already inside the same project network, set networks to that private network and omit floating_ip_network; the build then uses no floating IP.

Add provisioners#

The shell provisioner above applies updates and installs the guest agent. To produce a hardened golden image, run a configuration management tool against the build instance with the Ansible provisioner:

HCL
  provisioner "ansible" {
    playbook_file = "./hardening.yml"
    user          = "ubuntu"
  }

Packer connects over the same SSH session it already opened, so the playbook runs against the build instance with no extra inventory. Keep provisioners idempotent so a rebuilt image matches a prior build.

Build the image#

Initialize the working directory to download the declared plugin, then run the build:

bash
packer init golden-image.pkr.hcl
packer build golden-image.pkr.hcl

Packer launches the volume-backed build instance, runs the provisioners, stops the instance, and saves a snapshot to the Images service. On success it prints the new image ID:

==> Builds finished. The artifacts of successful builds are:
--> openstack.golden: An image was created: a1b2c3d4-5e6f-7890-ab12-cd34ef567890

Packer deletes the build instance, its volume, and the temporary floating IP after it saves the image.

Verify the published image#

Confirm the build registered an active image in the Images service.

In the CLI, show the image Packer named:

bash
openstack image show "golden-ubuntu-2404-20260604-1530" -c id -c status -c disk_format

The status field reads active when the image is ready to launch. For the full set of image fields and properties, see the Images service CLI reference and the Images service API reference.

To verify in the Console, go to Compute > Images. The image appears in the list with status Active, named with the timestamp from the build.

Version and name your images#

The template names each image with a build timestamp (golden-ubuntu-2404-${local.timestamp}), so every build produces a distinct, sortable image and earlier builds stay available for rollback. Keep the base distribution and version in the name so the source is clear at a glance. Prune old builds when you no longer need them:

bash
openstack image delete OLD_IMAGE_ID

Use the image to launch an instance#

Launch an instance from the golden image the same way you launch from any image. Because Quake AI flavors have disk=0, set the root volume size with --boot-from-volume at create time:

bash
openstack server create \
  --image "golden-ubuntu-2404-20260604-1530" \
  --flavor s1a.small \
  --network YOUR_PRIVATE_NETWORK_ID \
  --key-name YOUR_KEY_NAME \
  --boot-from-volume 20 \
  my-instance

For the full instance workflow across the Console, CLI, API, and Terraform, see Create a virtual machine instance.

See also#

Usage Guidelines

The sample code, software libraries, command line tools, proofs of concept, templates, and other related technology on this page (including any of the foregoing that is provided by Quake AI personnel) is provided to you as Quake AI Content under the Quake AI Customer Agreement, or the relevant written agreement between you and Quake AI (whichever applies). Do not use this Quake AI Content in your production accounts, or on production or other critical data. You are responsible for testing, securing, and optimizing the Quake AI Content (such as sample code) as appropriate for production grade use based on your specific quality control practices and standards. Deploying Quake AI Content may incur Quake AI charges for creating or using Quake AI chargeable resources, such as running Compute instances or storing data in Object Storage. Your use is also subject to the Acceptable Use Policy.

For the full policy, see Usage Guidelines.

Last validated: 19.06.2026

Quick answers

Was this page helpful?