Skip to content

Packer — Footguns

Baking secrets into images

The image is an artifact. It gets copied, shared, stored in registries. If you bake a database password, API key, or TLS private key into it, that secret lives in every copy of that image forever.

Fix: Inject secrets at runtime via cloud-init user-data, Vault agent, or instance metadata. The image should contain zero secrets.

Forgetting to clean up SSH artifacts

Packer creates a temporary SSH keypair and injects it into the build instance. Some builders clean up; some don't. If authorized_keys still has the build key in the final image, anyone with that key can SSH into every instance launched from it.

Fix: Add a cleanup provisioner as the last step:

provisioner "shell" {
  inline = [
    "rm -f /home/*/.ssh/authorized_keys",
    "rm -f /root/.ssh/authorized_keys",
    "sudo truncate -s 0 /etc/machine-id"
  ]
}

Also clear /tmp, shell history, and /etc/machine-id so cloned instances get unique IDs.

Not testing images before promotion

You built an AMI. You pushed it to production. It doesn't boot because a systemd unit has a typo. You find out at 3 AM.

Fix: Boot the image in CI, run smoke tests (Goss, InSpec, or plain curl), tear down, and only then copy/tag for production use.

Builder-specific gotchas

AMI copy regions: If you build in us-east-1 and need the AMI in eu-west-1, use ami_regions in the source block. But each copy takes time and creates a separate AMI ID. Track all of them in your manifest.

Azure Shared Image Gallery: Versioning is mandatory. If you don't increment the version, the publish fails silently or overwrites. Automate version bumps.

GCP image families: Using image_family is convenient, but the latest image in the family wins. A bad image becomes the default instantly unless you have a promotion gate.

Leaving build artifacts on disk

Local builders (QEMU, VirtualBox) produce multi-gigabyte image files in the output-* directory. Run ten builds and you've eaten 50 GB.

Fix: Add cleanup to your CI pipeline. Use packer build -force to overwrite previous output directories. Don't build locally if you can help it.

Not pinning provisioner versions

# Don't do this
curl -fsSL https://get.docker.com | sh
apt-get install -y nginx

Three months from now, this installs a different Docker version, a different Nginx version. Your "golden image" is no longer reproducible.

Fix: Pin everything. apt-get install -y nginx=1.24.0-1~jammy. Use checksums on downloaded binaries. If you use Ansible, pin collection and role versions in requirements.yml.

Skipping validate

packer build will fail 8 minutes into a 10-minute build because of a syntax error you could have caught in 2 seconds.

Fix: Always run packer validate . before packer build .. Add it to your CI pipeline as a separate step. Also run packer fmt -check . to catch formatting drift.

Confusing JSON templates with HCL2

Packer supported JSON templates for years. HCL2 became the default in Packer 1.7+. JSON templates still work but:

  • New features are HCL2-only.
  • Plugin ecosystem assumes HCL2.
  • JSON templates cannot use for_each, dynamic, or most functions.

If you're reading a tutorial and it uses JSON (template.json), translate it to HCL2. Use packer hcl2_upgrade to convert old JSON templates, then review the output manually — the conversion is not perfect.