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¶
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.