Packer’s "build args" are a surprisingly powerful way to inject dynamic values into your image builds without touching your Packer templates directly.

Let’s see it in action. Imagine you have a Packer template (my-template.pkr.hcl) that needs to know a specific version of an application to install. Instead of hardcoding version = "1.2.3", you want to pass it in from the command line.

Here’s a snippet of the Packer template:

variable "app_version" {
  type    = string
  default = "latest"
}

build {
  sources {
    source "amazon-ebs" {

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

      instance_type = "t3.micro"
      region        = "us-east-1"
      source_ami    = "ami-0c55b159cbfafe1f0" # Example Ubuntu 20.04 LTS
      ssh_username  = "ubuntu"
    }
  }

  provisioner "shell" {
    inline = [

      "echo 'Installing app version: {{user `app_version`}}'",


      "curl -L -o /tmp/app.tar.gz https://example.com/app-{{user `app_version`}}.tar.gz",

      "tar -xzf /tmp/app.tar.gz -C /opt/",
      "rm /tmp/app.tar.gz"
    ]
  }
}

When you run Packer, you can pass the app_version like this:

packer build -var 'app_version=1.2.3' my-template.pkr.hcl

During the build, Packer will substitute {{user app_version}} with 1.2.3. The echo command will output Installing app version: 1.2.3, and the curl command will attempt to download https://example.com/app-1.2.3.tar.gz.

The problem Packer solves here is the need for repeatable, parameterized image builds. Without build args, you’d be tempted to duplicate your Packer templates for each version or use complex scripting outside of Packer to manage your template files. Build args keep your templates clean and your build process straightforward.

Internally, Packer processes the -var (or -var-file) flags first, populating its internal variable store. When it encounters a {{user ...}} interpolation within a template, it looks up the variable name in its store. If found, it substitutes the value. If not found and a default is defined, it uses the default. If neither is found, it errors.

The exact levers you control are the variable names you define in your Packer template using the variable block and the corresponding -var flags you pass on the command line. You can define types (string, number, bool, list, map, any) and defaults, which are crucial for making your templates more robust.

You can also pass multiple variables:

packer build \
  -var 'app_version=1.2.4' \
  -var 'instance_type=t3.medium' \
  my-template.pkr.hcl

And use environment variables:

export PK_APP_VERSION="1.2.5"

packer build -var 'app_version={{env `PK_APP_VERSION`}}' my-template.pkr.hcl

This {{env ...}} interpolation is particularly useful for integrating Packer into CI/CD pipelines where build parameters are often managed as environment variables.

A common pitfall is misunderstanding the scope of {{user ...}}. It’s designed for variables passed from the user on the command line or via -var-file. For variables that are intrinsic to the Packer build itself (like timestamp or build_name), you’d use different interpolations.

The next concept you’ll likely encounter is the use of -var-file to manage sets of variables, especially when dealing with sensitive information or complex configurations that are too cumbersome for the command line.

Want structured learning?

Take the full Packer course →