Packer HCL2 Source/Build Separation for AWS AMI Pipelines
Separate Packer builder definitions from build pipelines, handle sensitive variables safely, and clean up failed AWS AMI builds.
30 Oct 2025, 18:14 UTC

Problem and takeaway
Many Packer templates mix builder settings, provisioners, and secrets into a single file. That makes it hard to reuse one base AMI across multiple image variants, and it increases the risk of committing credentials to version control.
Use Packer HCL2 source/build separation: define the builder once in a source block, reference it from one or more build blocks, mark secrets with sensitive = true, and inject credentials from outside the template. This keeps builder definitions reusable and reduces secret exposure, though Packer redaction does not protect secrets printed by provisioners.
Requirements
- Packer 1.7 or later for HCL2
sourceandbuildblocks. - AWS credentials with permission to launch temporary EC2 instances, create AMIs, create snapshots, and manage security groups.
- A base AMI ID in each target region.
- A method for external secret injection, such as environment variables or a CI secret store.
Smallest suitable design
One source block per base AMI, one build block per image variant, a shell or Ansible provisioner for hardening, and tags on every resource for cost allocation and cleanup.
variable "source_ami" { type = string }
variable "instance_type" { type = string }
variable "aws_region" { type = string }
variable "ssh_username" { type = string }
variable "build_tag" { type = string }
variable "database_password" { type = string; sensitive = true }
source "amazon-ebs" "base" {
ami_name = "packer-base-${var.build_tag}"
instance_type = var.instance_type
source_ami = var.source_ami
region = var.aws_region
ssh_username = var.ssh_username
tags = {
PackerBuild = var.build_tag
ManagedBy = "packer"
}
}
build {
sources = ["source.amazon-ebs.base"]
provisioner "shell" {
inline = [
"sudo yum update -y",
"sudo systemctl enable sshd"
]
}
}
Run validation from the directory containing the template:
packer init .
packer validate -var-file=secrets.pkrvars.hcl template.pkr.hcl
Where to run: a workstation or CI runner with the Packer binary and AWS credentials available. Required permissions include ec2:RunInstances, ec2:CreateTags, ec2:CreateImage, ec2:DeregisterImage, and ec2:DeleteSnapshot. Expected check: packer validate exits zero and reports compatible plugins. Risk: validation does not confirm runtime IAM permissions or catch resources abandoned after a failed build.
Trust and data boundaries
Packer needs cloud credentials with sufficient IAM permissions. Inject them through environment variables such as AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY, or through an external secret injector in CI. Never hardcode access keys in HCL files.
The sensitive attribute tells Packer to redact the value from its own UI output and logs. The value still exists in the template file and in the process environment. More importantly, provisioner output is outside Packer's redaction scope: a shell script that echoes ${database_password} will write the secret to provisioner logs. Treat provisioner logs as potentially unredacted.
Cross-account AMI sharing creates another data boundary. Granting launch permission to another AWS account makes the AMI available to that account; revoking it requires separate automation and does not automatically stop instances already launched from the image.
Operational checks
- Run
packer versionand confirm the installed Packer supports HCL2 source/build blocks. - Pin Packer and provider plugins in a
required_pluginsblock so pipeline runs are reproducible. - Inspect CloudTrail or Packer output after a build for redacted sensitive values and for orphaned instances, snapshots, or security groups.
- Check provisioner logs separately to confirm secrets are not printed outside Packer's redaction.
- Tag resources with build ID and owner so cleanup jobs can identify orphaned resources.
Failure modes
A failed amazon-ebs build can leave a running temporary EC2 instance, an unattached snapshot, or a security group behind. These resources continue to incur cost and may violate retention policies. Packer attempts cleanup on some failures, but not all failure modes—such as a provisioner failure after instance creation—guarantee removal.
If a build succeeds but the AMI is not registered correctly, downstream launch failures may appear only when an Auto Scaling group tries to use the image. Verify AMI state with aws ec2 describe-images --image-ids ami-... before promoting the image.
Conditions that would change the design
- Windows images: replace SSH with WinRM and adjust the builder and provisioner accordingly.
- Cross-account distribution: add an AMI launch permission post-processor and a revocation path for when access changes.
- Strict compliance: replace inline shell provisioning with Ansible or Chef so hardening is versioned separately from image builds.
- Long-lived pipelines: pin Packer and plugin versions; HCL2 syntax changed significantly from legacy JSON and older Packer versions may not parse it.
Limitation and verification
Packer's sensitive redaction covers Packer output only. The practical check is to run a build in a sandbox account with a short-lived IAM role, then inspect provisioner logs and CloudTrail for leaked values and orphaned resources. There is no automatic rollback for a failed Packer build because Packer creates mutable cloud resources; cleanup must be tag-based or handled by an external garbage-collection job.