Packer doesn’t actually mask sensitive variables in its output by default; it prints them in plain text.

Let’s say you’re building an AMI with Packer and you need to pass in a private key or an API token. If you put that directly into your Packer template as a variable, like this:

variable "ssh_private_key" {
  type    = string
  default = "----BEGIN RSA PRIVATE KEY----\\n...your private key...\\n----END RSA PRIVATE KEY----"
}

And then use it:

provisioner "shell" {
  inline = ["echo ${var.ssh_private_key}"]
}

When Packer runs, you’ll see your entire private key printed out in the build logs. Not ideal for secrets.

The mechanism Packer provides for handling secrets is the sensitive variable attribute. When you mark a variable as sensitive = true, Packer will attempt to hide its value in the output.

Here’s how you’d declare a sensitive variable:

variable "api_token" {
  type      = string
  sensitive = true
}

And then you’d typically set its value using environment variables or a vars_file. For instance, using an environment variable:

export PKKR_API_TOKEN="your_super_secret_token_here"
packer build my-template.pkr.hcl

Now, when Packer uses var.api_token, it shouldn’t print the actual token to the console. Instead, you’ll see (sensitive) where the value would normally be.

...
==> provisioner.shell: echo (sensitive)
...

This is the core idea: sensitive = true tells Packer "don’t show this value in logs."

However, and this is where it gets tricky, Packer’s "masking" is limited. It only prevents the value from being printed directly in the standard build output. If any part of your build process, like a shell provisioner, explicitly echoes the sensitive variable, it will be printed.

Consider this shell provisioner:

provisioner "shell" {
  inline = [
    "echo 'My API Token is: ${var.api_token}'"
  ]
}

Even with api_token marked as sensitive = true, the output will reveal the token:

...
==> provisioner.shell: My API Token is: your_super_secret_token_here
...

This is because the shell provisioner is executing a command that, by its nature, prints its arguments. Packer has already substituted the sensitive value into the command string before it’s sent to the remote machine for execution.

The most common way to actually keep secrets from being exposed is to avoid printing them directly in provisioners. If you need to use a secret in a script, pass it as an environment variable to the script or the command being run, rather than as a command-line argument or directly in an echo.

For example, if you’re using curl with an API token:

variable "api_token" {
  type      = string
  sensitive = true
}

provisioner "shell" {
  inline = [
    "curl -H 'Authorization: Bearer ${var.api_token}' https://api.example.com/data"
  ]
}

This will expose the token. A better approach is:

variable "api_token" {
  type      = string
  sensitive = true
}

provisioner "shell" {
  inline = [
    "export API_TOKEN=${var.api_token}",
    "curl -H 'Authorization: Bearer $API_TOKEN' https://api.example.com/data",
    "unset API_TOKEN" # Good practice to clean up
  ]
}

Here, var.api_token is still substituted into the export command, but the sensitive value is then stored in an environment variable (API_TOKEN) on the target machine. The curl command then reads from this environment variable. While the export command itself might show up in some very low-level execution logs on the target, it’s less likely to be printed in the main Packer build output. The unset command is crucial for preventing the variable from lingering in the shell environment.

Another critical point is how you manage the source of your sensitive variables. Using vars_files is convenient but requires careful handling of those files. If my-secrets.pkrvars.hcl contains api_token = "your_super_secret_token_here", and you commit this file to Git, your secret is compromised. Always add sensitive .pkrvars.hcl files to your .gitignore.

The most secure method for providing sensitive variables is often through environment variables, as they are not typically written to disk or committed to version control. You can also use secrets management tools (like HashiCorp Vault, AWS Secrets Manager, etc.) and integrate them into your Packer build pipeline, though this is more advanced. Packer has specific plugins for interacting with these services.

Finally, be aware that even if Packer’s output is clean, the image itself might inadvertently contain secrets if you’re not careful during the build. For example, if a shell provisioner copies a file containing a secret into the image, that secret will be baked into the AMI. Always review your provisioner scripts to ensure no sensitive data is being persisted in the final artifact.

The next hurdle you’ll likely face is managing multiple sensitive variables across different environments or teams, which often leads to exploring secrets management integrations.

Want structured learning?

Take the full Packer course →