Packer’s templating system is surprisingly powerful, but its debugging story is often about piecing together cryptic output rather than a clear "aha!" moment.

Let’s say you’re building an AMI with Packer and you’re trying to pass some dynamic values into your user_data script. You’ve got a variables block, you’re referencing them with {{ .Vars.my_variable }}, and it’s just not working. The build fails, and you’re staring at a bunch of user_data output that looks like garbage, or worse, the build completes but the application inside the AMI is misconfigured.

Here’s a common scenario:

{
  "variables": {
    "region": "us-east-1",

    "ami_name": "my-app-{{timestamp}}"

  },
  "builders": [
    {
      "type": "amazon-ebs",

      "region": "{{ .Vars.region }}",


      "ami_name": "{{ .Vars.ami_name }}",

      "source_ami_filter": {
        "filters": {
          "name": "ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-20230101",
          "virtualization-type": "hvm"
        },
        "owners": ["099720109477"]
      },
      "ssh_username": "ubuntu",
      "instance_type": "t3.micro",
      "ami_description": "My awesome app AMI",
      "tags": {

        "Name": "{{ .Vars.ami_name }}"

      }
    }
  ],
  "provisioners": [
    {
      "type": "shell",
      "inline": [

        "echo \"Region is: {{ .Vars.region }}\"",


        "echo \"AMI Name is: {{ .Vars.ami_name }}\""

      ]
    }
  ]
}

You run packer build my-template.json and you see output like this in your build logs:

==> amazon-ebs: Running provisioner: shell

    amazon-ebs: Running shell command: echo "Region is: {{ .Vars.region }}"


    amazon-ebs: Region is: {{ .Vars.region }}


    amazon-ebs: Running shell command: echo "AMI Name is: {{ .Vars.ami_name }}"


    amazon-ebs: AMI Name is: {{ .Vars.ami_name }}

This tells you Packer isn’t even seeing your variables. It’s just passing the template strings directly into the shell.

The Real Culprit: Variable Scope and Interpolation Order

Packer has several places where it interpolates variables:

  1. Environment Variables: PKR_VAR_variable_name

  2. File Variables: -var 'variable_name=value' or -var-file=path/to/vars.json

  3. Template Variables: Defined within the variables block of your template.

  4. Built-in Variables: Like {{timestamp}}, {{build_id}}, {{.SourceAMI}}, etc.

The problem often arises when you try to interpolate a template variable within another template variable or a string that Packer isn’t expecting to be templated. The shell provisioner’s inline directive is a common place for this to go wrong. Packer interpolates the entire inline string once before passing it to the shell. If your variable isn’t defined or is referenced incorrectly, you get the literal string.

Debugging Steps and Solutions

Let’s go through the common causes and how to fix them:

  1. Incorrect Variable Name or Typo:

    • Diagnosis: Double-check the spelling of your variable name in the variables block and where you’re referencing it. Case matters!

    • Fix: Ensure {{ .Vars.variable_name }} exactly matches the key in your variables block.

    • Why it works: Packer uses exact string matching for variable names. A typo means it can’t find the variable, so it leaves the placeholder.

  2. Variable Not Defined (or Overridden):

    • Diagnosis: Run packer inspect template.json. This command will show you the final resolved values of all variables before the build starts. If your variable is missing or has an unexpected value, it wasn’t set correctly.

    • Fix:

      • Explicitly set it on the command line: packer build -var 'region=us-west-2' -var 'ami_name=my-app-{{timestamp}}' template.json

      • Use a -var-file: Create a vars.json file:

        {
          "region": "us-west-2",
        
          "ami_name": "my-app-{{timestamp}}"
        
        }
        

        Then run: packer build -var-file=vars.json template.json

      • Check environment variables: Ensure you haven’t defined a conflicting environment variable like PKR_VAR_region with an unwanted value.

    • Why it works: Packer has a precedence order for variable definition (environment > command line > var file > template variables block). packer inspect shows you the result of this precedence. Explicitly defining it ensures it’s present.

  3. Using a Template Variable Where Packer Doesn’t Expect Templating:

    • Diagnosis: The shell provisioner’s inline directive is a prime suspect. Packer renders the entire string before passing it to the shell. If you put {{ .Vars.my_variable }} inside a string that’s already being interpreted by Packer, it can get confused.

    • Fix: Use the script option instead of inline for more complex shell logic, or escape the interpolation if you intend for the target system to interpret it (though this is rare for Packer variables). A more common fix is to use echo within the inline command itself, as shown in the example. The issue in the example above is that the echo command itself is not being run by Packer’s template engine for the content of the echo.

    • Corrected provisioners block for the example:

      "provisioners": [
        {
          "type": "shell",
          "inline": [
      
            "echo \"Region is: {{ .Vars.region }}\"",
      
      
            "echo \"AMI Name is: {{ .Vars.ami_name }}\""
      
          ]
        }
      ]
      

      This works because Packer interpolates {{ .Vars.region }} and {{ .Vars.ami_name }} before passing the entire echo "Region is: us-east-1" string to the shell. The original problem was a misunderstanding of how inline works with echo.

    • Why it works: Packer’s templating engine processes variables before executing the provisioner. By ensuring the variable is correctly placed within a string that Packer will template (like the arguments to echo here), it gets substituted.

  4. Trying to Interpolate a Variable Inside Another Interpolated Value (e.g., {{ .Vars.ami_name }} in a tags value):

    • Diagnosis: Packer’s tags block is a good example. It does support templating. However, if you try to do something like {{ .Vars.ami_name }} within a variable definition that itself uses templating, you can run into issues.

    • Fix: Ensure your variable definitions are straightforward. If you need to build a complex string, define it as a separate variable.

      "variables": {
        "base_name": "my-app",
      
        "ami_name": "{{ .Vars.base_name }}-{{timestamp}}" // This is fine
      
      },
      "builders": [
        {
          "type": "amazon-ebs",
          // ... other config ...
      
          "ami_name": "{{ .Vars.ami_name }}", // Referencing the constructed variable
      
          "tags": {
      
            "Name": "{{ .Vars.ami_name }}" // Referencing it again for tags
      
          }
        }
      ]
      
    • Why it works: Packer resolves ami_name first, which itself has a template. Then, it uses that resolved value in the tags block. It doesn’t try to re-interpolate the ami_name variable’s definition within the tags block.

  5. Using Variables in source_ami_filter or other complex structures:

    • Diagnosis: Some fields within Packer’s configuration, especially those that are maps or lists of maps, require specific interpolation.
    • Fix: Refer to the Packer documentation for the specific builder or provisioner. For example, in amazon-ebs’s source_ami_filter, you can use variables directly:
      "source_ami_filter": {
        "filters": {
          "name": "ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-20230101",
          "virtualization-type": "hvm"
        },
      
        "owners": ["{{ .Vars.ami_owner }}"] // Example using a variable
      
      }
      
      And define ami_owner elsewhere.
    • Why it works: Packer parses these complex structures and applies its templating engine to the appropriate string values within them.
  6. Packer Version Issues (Rare):

    • Diagnosis: If you’re on a very old Packer version, there might be bugs or limitations in variable interpolation.
    • Fix: Upgrade to the latest stable Packer release.
    • Why it works: Newer versions often include bug fixes and improvements to the templating engine.

After fixing these, you’ll likely encounter the next common issue: your user_data script expecting a variable that the Packer shell provisioner didn’t pass through correctly, or a dependency that wasn’t installed.

Want structured learning?

Take the full Packer course →