# How to build a golden VM image with Packer

Source: https://docs.quake.ai/docs/compute/how-to/build-golden-image-packer
Markdown: https://docs.quake.ai/docs/compute/how-to/build-golden-image-packer.md

---

# How to build a golden VM image with Packer

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



Packer runs wherever you run it, on a workstation or a CI runner, and calls the Quake AI API to launch a temporary build instance, provision it, and save the result to the Images service. This page covers that build-from-Packer path. To register an image you already have as a file or snapshot, see [Create a virtual machine image](/docs/compute/how-to/create-image) instead.



## Prerequisites

- A Quake AI account with a project
- An [application credential](/docs/tools/generate-app-credentials) for the build, with the roles Packer needs to create instances, volumes, floating IPs, and images
- The [OpenStack CLI installed](/docs/tools/install-openstack-client) 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


These commands use bash. See [Set up a Linux CLI environment on Windows](/docs/tools/windows-cli-environment) for setup options including WSL, Git Bash, and Docker.


## Install Packer and the OpenStack plugin

Install Packer with your package manager or from the [Packer downloads page](https://developer.hashicorp.com/packer/install), 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](/docs/tools/generate-app-credentials) 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](/docs/network/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](/reference/compute/flavors-cli).

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

```text
==> 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](/reference/compute/images-cli) and the [Images service API reference](/reference/compute/images-api).

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](/docs/compute/how-to/create-instance).

## See also

- [Create a virtual machine image](/docs/compute/how-to/create-image)
- [Create a virtual machine instance](/docs/compute/how-to/create-instance)
- [Create application credentials](/docs/tools/generate-app-credentials)
- [Images service CLI reference](/reference/compute/images-cli)
- [Images service API reference](/reference/compute/images-api)
- [Packer OpenStack builder documentation](https://developer.hashicorp.com/packer/integrations/hashicorp/openstack)
