- datacenter
- l1
- topic-pack
- packer --- Portal | Level: L1: Foundations | Topics: Packer | Domain: Datacenter & Hardware
Packer — Primer¶
What Packer does¶
Packer builds identical machine images for multiple platforms from a single source configuration. You define what the image should contain once. Packer produces an AMI, a GCP image, a Vagrant box, a Docker image — whatever you need — from that single definition.
It is not a configuration management tool. It does not run on live servers. It runs once, produces an artifact (the image), and exits.
Name origin: Packer was created by Mitchell Hashimoto (HashiCorp co-founder) in 2013. The name describes its function — it "packs" software and configuration into a machine image. It was one of HashiCorp's earliest tools, preceding Terraform (2014) and Consul (2014).
Fun fact: Before Packer, teams typically built images by booting a VM, manually installing software, and snapshotting — a non-reproducible process called "golden image by hand." Packer made image building scriptable, version-controlled, and CI-pipeline-friendly.
Why it matters¶
- Golden images. Every server starts from a known, tested baseline.
- Immutable infrastructure. Deploy new images instead of patching running servers.
- Consistency. Dev, staging, and production boot from the same image. Drift disappears.
- Speed. Launching a pre-baked image is faster than bootstrapping from a bare OS at boot.
Core concepts¶
| Concept | Role |
|---|---|
| Template | HCL2 file(s) defining the entire build |
| Builder | Plugin that creates the image for a specific platform (AWS, GCP, Docker, etc.) |
| Provisioner | Runs inside the build to install/configure software (shell, Ansible, file copy) |
| Post-processor | Acts on the finished artifact (generate manifest, push Docker image, create Vagrant box) |
HCL2 template structure¶
Two main block types:
Source blocks — define the builder and its platform-specific config (AMI base, instance type, region).
Timeline: Packer originally used JSON templates (
.json). HCL2 support was added in Packer 1.5 (2020) and became the recommended format. JSON templates still work but lack variables, locals, and functions. If you see a Packer template in JSON, it is legacy — migrate to HCL2 withpacker hcl2_upgrade.
source "amazon-ebs" "ubuntu" {
ami_name = "app-{{timestamp}}"
instance_type = "t3.micro"
region = "us-east-1"
source_ami_filter {
filters = {
name = "ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"
root-device-type = "ebs"
virtualization-type = "hvm"
}
owners = ["099720109477"] # Canonical
most_recent = true
}
ssh_username = "ubuntu"
}
Build blocks — reference sources and attach provisioners/post-processors.
build {
sources = ["source.amazon-ebs.ubuntu"]
provisioner "shell" {
inline = [
"sudo apt-get update",
"sudo apt-get install -y nginx"
]
}
post-processor "manifest" {
output = "manifest.json"
}
}
Builders¶
| Builder | Produces | Notes |
|---|---|---|
amazon-ebs |
AMI | Most common AWS builder. Launches instance, provisions, snapshots. |
azure-arm |
Managed Image / Shared Image Gallery | Needs service principal or managed identity. |
googlecompute |
GCP Image | Uses service account JSON or application default credentials. |
docker |
Docker image | Useful when you need provisioners Dockerfile can't express. |
virtualbox-iso |
OVF/OVA | Boots from ISO, good for local dev images. |
qemu |
QCOW2 | KVM/libvirt images. Headless builds. |
proxmox-iso |
Proxmox template | Homelab favorite. Integrates with Proxmox API. |
Provisioners¶
| Provisioner | What it does |
|---|---|
shell |
Runs inline commands or a script file. The workhorse. |
ansible |
Runs an Ansible playbook against the build instance over SSH. |
file |
Copies files/directories from host into the image. |
powershell |
Shell equivalent for Windows builds. |
Provisioners run in order. If one fails, the build fails.
Gotcha: Shell provisioners run in a non-interactive, non-login shell by default. Your
.bashrcand.profileare NOT sourced. Environment variables you expect from login shells will be missing. Useinline_shebangor explicitsource /etc/profileif you need login environment. Also,apt-getmust use-y— there is no terminal to accept prompts.Remember: Packer provisioner order: "FAP" — File (copy configs in), Ansible (configure the system), Post-processor (output the artifact). In practice, you typically run shell provisioners to install packages, then Ansible for configuration, then file provisioners for last-mile config.
Post-processors¶
| Post-processor | What it does |
|---|---|
manifest |
Writes build metadata (artifact ID, builder, timestamp) to JSON. |
docker-push |
Pushes the built Docker image to a registry. |
vagrant |
Packages the artifact as a .box file. |
Variables and locals¶
Define variables in the template:
variable "aws_region" {
type = string
default = "us-east-1"
}
locals {
ami_name = "app-${formatdate("YYYYMMDD-hhmm", timestamp())}"
}
Set values via:
- -var 'aws_region=us-west-2' on the command line
- -var-file=prod.pkrvars.hcl for a file of values
- PKR_VAR_aws_region environment variable
Packer + Ansible¶
provisioner "ansible" {
playbook_file = "./ansible/site.yml"
extra_arguments = [
"--extra-vars", "env=production",
"--vault-password-file", "/path/to/vault-pass"
]
}
Packer creates a temporary SSH key, passes connection details to Ansible. Ansible runs against the build instance exactly like any other target.
Packer + Terraform¶
Standard pattern:
1. Packer builds the AMI, writes the AMI ID to manifest.json.
2. Terraform reads the AMI ID (or uses aws_ami data source with filters).
3. Terraform launches instances from the golden image.
Packer owns the image. Terraform owns the infrastructure. Clean boundary.
Image pipeline¶
code change -> CI triggers Packer build -> image created
-> automated tests (boot, smoke, compliance)
-> promote to production account/project
-> Terraform deploys new instances from promoted image
Build vs runtime configuration¶
Bake into the image (Packer): - OS packages, agents, base software - Hardening, CIS benchmarks - Static config that never changes between environments
Configure at boot (cloud-init, user-data): - Secrets, credentials, tokens - Environment-specific settings (hostnames, endpoints) - Service registration, cluster join
Rule of thumb: if it changes between environments, it does not belong in the image.
Remember: The bake-vs-boot decision mnemonic: "BOSS" — Binaries bake, OS config bake, Secrets boot, Settings (env-specific) boot. If it is the same across all environments, bake it. If it varies, inject it at boot via cloud-init or user-data.
Packer vs Docker: Packer builds VM images (AMIs, QCOW2). Docker builds container images. Use Packer when you need a full OS with kernel, boot process, and system services. Use Docker when you need a single application process. Many teams use both: Packer for the base AMI (OS + agents + hardening), Docker for application packaging on top.
Key commands¶
| Command | What it does |
|---|---|
packer init . |
Downloads required plugins defined in required_plugins blocks. |
packer fmt . |
Formats HCL2 files to canonical style. |
packer validate . |
Checks template syntax and config without building. |
packer build . |
Runs the build. The main event. |
packer build -only='amazon-ebs.ubuntu' . |
Build a single source when multiple are defined. |
packer build -var-file=prod.pkrvars.hcl . |
Build with a specific variable file. |
Always run validate before build. Make fmt part of your CI lint step.
Debug clue: When a Packer build fails mid-provisioner, the build instance is usually terminated, making it hard to debug. Use
packer build -on-error=ask .— this pauses on failure and lets you SSH into the instance to inspect state. For CI, use-on-error=abort(the default) to fail fast and clean up.Interview tip: "How do you ensure your AMIs are secure and up to date?" Strong answer: Packer pipeline builds weekly from the latest base AMI, runs CIS benchmark hardening via Ansible, validates with InSpec/goss tests, and promotes through dev -> staging -> prod accounts. Old AMIs are deregistered after 90 days.