Packer’s only and except directives give you fine-grained control over which builders run during a build, a surprisingly powerful feature for managing complex build configurations.

Let’s see this in action. Imagine you have a Packer template that defines builds for AWS, Azure, and VMware, but for a specific testing run, you only want to build for AWS.

Here’s a snippet of a Packer template (template.pkr.hcl):

variable "aws_region" {
  type    = string
  default = "us-east-1"
}

variable "azure_location" {
  type    = string
  default = "East US"
}

variable "vmware_datacenter" {
  type    = string
  default = "MyDatacenter"
}

source "amazon-ebs" "aws_example" {
  region = var.aws_region

  ami_name = "my-aws-ami-{{timestamp}}"

  source_ami_filter {
    filters = {
      name = "ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"
      root-device-type = "ebs"
    }
    most_recent = true
    owners      = ["099720109477"] # Canonical's Ubuntu AMI ID
  }
  instance_type = "t3.micro"
}

source "azure-arm" "azure_example" {
  client_id       = var.azure_client_id
  client_secret   = var.azure_client_secret
  tenant_id       = var.azure_tenant_id
  subscription_id = var.azure_subscription_id
  location        = var.azure_location
  vm_size         = "Standard_DS1_v2"
  image_urn       = "Canonical:0001-com-ubuntu-focal-20-04-lts:20_04-lts-gen2:latest"
  os_type         = "Linux"
  build_resource_group_name = "packer-build-rg"
}

source "vmware-iso" "vmware_example" {
  communicator = "ssh"

  vm_name      = "my-vmware-vm-{{timestamp}}"

  datacenter   = var.vmware_datacenter
  cluster      = "MyCluster"
  resource_pool = "MyResourcePool"
  network_adapters {
    network_name = "VM Network"
  }
  iso_paths = ["/path/to/ubuntu-20.04.3-desktop-amd64.iso"]
  guest_os_type = "ubuntu-64"
}

build {
  sources = [
    "source.amazon-ebs.aws_example",
    "source.azure-arm.azure_example",
    "source.vmware-iso.vmware_example"
  ]

  provisioner "shell" {
    inline = [
      "echo 'Hello from Packer!'"
    ]
  }
}

By default, running packer build template.pkr.hcl would attempt to build for AWS, Azure, and VMware.

To restrict this to only the AWS builder, you’d use the only directive within the build block:

build {
  sources = [
    "source.amazon-ebs.aws_example",
    "source.azure-arm.azure_example",
    "source.vmware-iso.vmware_example"
  ]

  only = ["amazon-ebs.aws_example"] # <-- This line is key

  provisioner "shell" {
    inline = [
      "echo 'Hello from Packer!'"
    ]
  }
}

Now, packer build template.pkr.hcl will only execute the amazon-ebs.aws_example builder. The Azure and VMware builders will be skipped entirely.

Conversely, if you wanted to build for everything except AWS, you’d use except:

build {
  sources = [
    "source.amazon-ebs.aws_example",
    "source.azure-arm.azure_example",
    "source.vmware-iso.vmware_example"
  ]

  except = ["amazon-ebs.aws_example"] # <-- This line is key

  provisioner "shell" {
    inline = [
      "echo 'Hello from Packer!'"
    ]
  }
}

This would build for Azure and VMware, but skip AWS.

You can specify multiple builders for only or except by providing a comma-separated list. For example, to build only for AWS and Azure:

build {
  sources = [
    "source.amazon-ebs.aws_example",
    "source.azure-arm.azure_example",
    "source.vmware-iso.vmware_example"
  ]

  only = ["amazon-ebs.aws_example", "azure-arm.azure_example"] # <-- Multiple builders

  provisioner "shell" {
    inline = [
      "echo 'Hello from Packer!'"
    ]
  }
}

The names used in only and except are the source type followed by the builder name as defined in the source blocks, separated by a dot. So, amazon-ebs.aws_example refers to the builder defined by source "amazon-ebs" "aws_example".

This mechanism is invaluable when you have a single Packer template that codifies builds for multiple environments (dev, staging, prod) or cloud providers, but need to selectively trigger specific parts of the build process. For instance, you might have a template with builders for AWS, GCP, and Azure, but during a quick local test, you only want to spin up an AWS instance to verify a small change without incurring costs or time on other platforms. Using packer build -only=amazon-ebs.aws_example template.pkr.hcl achieves this efficiently.

The only and except directives can also be specified on the command line, overriding any settings in the Packer template. This is extremely useful for ad-hoc builds or CI/CD pipelines where you want to dynamically control which builders are executed. For example:

packer build -only=source.amazon-ebs.aws_example template.pkr.hcl

or

packer build -except=source.azure-arm.azure_example,source.vmware-iso.vmware_example template.pkr.hcl

It’s important to note that the command-line flags take precedence. If you define only in your HCL and also specify -only on the command line, the command-line flag will be used, and the HCL only directive will be ignored. If both the HCL and command-line flags are used and they target different sets of builders, the command-line flags dictate the final execution plan.

The most surprising thing about only and except is how they interact with build targets when you have multiple build blocks in a single template. If you have multiple build blocks, and you use only or except on the command line, Packer will apply those filters to all build blocks. However, if you specify only or except within a build block in your HCL, those filters are scoped only to that specific build block and do not affect other build blocks in the same template.

This level of control allows for sophisticated template design where a single file can manage a wide range of build configurations, and you can selectively activate subsets of them without resorting to multiple template files or complex scripting.

The next concept you’ll likely encounter is using Packer with conditional logic and variables to dynamically select builders based on environment or other runtime factors.

Want structured learning?

Take the full Packer course →