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:
-
Environment Variables:
PKR_VAR_variable_name -
File Variables:
-var 'variable_name=value'or-var-file=path/to/vars.json -
Template Variables: Defined within the
variablesblock of your template. -
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:
-
Incorrect Variable Name or Typo:
-
Diagnosis: Double-check the spelling of your variable name in the
variablesblock and where you’re referencing it. Case matters! -
Fix: Ensure
{{ .Vars.variable_name }}exactly matches the key in yourvariablesblock. -
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.
-
-
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 avars.jsonfile:{ "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_regionwith an unwanted value.
-
-
Why it works: Packer has a precedence order for variable definition (environment > command line > var file > template
variablesblock).packer inspectshows you the result of this precedence. Explicitly defining it ensures it’s present.
-
-
Using a Template Variable Where Packer Doesn’t Expect Templating:
-
Diagnosis: The
shellprovisioner’sinlinedirective 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
scriptoption instead ofinlinefor 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 useechowithin theinlinecommand itself, as shown in the example. The issue in the example above is that theechocommand itself is not being run by Packer’s template engine for the content of the echo. -
Corrected
provisionersblock 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 entireecho "Region is: us-east-1"string to the shell. The original problem was a misunderstanding of howinlineworks withecho. -
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
echohere), it gets substituted.
-
-
Trying to Interpolate a Variable Inside Another Interpolated Value (e.g.,
{{ .Vars.ami_name }}in atagsvalue):-
Diagnosis: Packer’s
tagsblock 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_namefirst, which itself has a template. Then, it uses that resolved value in thetagsblock. It doesn’t try to re-interpolate theami_namevariable’s definition within thetagsblock.
-
-
Using Variables in
source_ami_filteror 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’ssource_ami_filter, you can use variables directly:
And define"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 }ami_ownerelsewhere. - Why it works: Packer parses these complex structures and applies its templating engine to the appropriate string values within them.
-
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.