Container images get most of the immutable-infrastructure attention these days, but VM images still matter. Bake an OS image with everything pre-installed, run identical instances from it, and you avoid the configuration drift that plagues script-based provisioning. Packer is the tool that makes this practical without writing custom AMI build pipelines.
This post is what Packer actually does, the patterns that hold up in production, and when image-based deployment is the right choice.
The Pitch
The traditional VM provisioning workflow: launch an empty Ubuntu instance, run cloud-init or Ansible to install everything, start the application. Each launch reruns the configuration. Each instance is slightly different by the end.
Packer's workflow: build an image once with everything installed. Each launch is a copy of that image. Instances are identical.
Benefits:
- Fast boot. No configuration time at launch; the instance is ready when it boots.
- Reproducible. All instances from the same image are byte-identical.
- Auditable. The image is a known artifact you can scan and verify.
- Drift-free. Configuration cannot drift; every instance is the same.
The cost: image build time, image storage, and the discipline of treating images as artifacts.
Packer Basics
A Packer template (HCL format):
packer {
required_plugins {
amazon = {
version = ">= 1.0"
source = "github.com/hashicorp/amazon"
}
}
}
source "amazon-ebs" "ubuntu" {
ami_name = "myapp-${var.app_version}-${formatdate("YYYYMMDDhhmmss", timestamp())}"
instance_type = "t3.medium"
region = "us-east-1"
source_ami_filter {
filters = {
name = "ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"
}
most_recent = true
owners = ["099720109477"] # Canonical
}
ssh_username = "ubuntu"
}
build {
sources = ["source.amazon-ebs.ubuntu"]
provisioner "shell" {
scripts = [
"scripts/install-deps.sh",
"scripts/install-app.sh",
"scripts/configure-monitoring.sh",
]
}
}
packer build template.pkr.hcl launches a temporary instance, runs the provisioner scripts, snapshots the volume, and registers an AMI. The AMI is now available to launch from.
Multi-Cloud Builds
Packer supports many builders. You can build the same image for multiple clouds in one run:
source "amazon-ebs" "ubuntu" { ... }
source "googlecompute" "ubuntu" { ... }
source "azure-arm" "ubuntu" { ... }
build {
sources = [
"source.amazon-ebs.ubuntu",
"source.googlecompute.ubuntu",
"source.azure-arm.ubuntu",
]
provisioner "shell" {
scripts = ["scripts/install.sh"]
}
}
The provisioner runs identically on all three. Each cloud's image is registered separately.
This is the multi-cloud parity story for VM images. Reliable across clouds, complex to maintain in practice.
What to Build Into the Image
The right amount of "baking" is a judgment call.
Always in the image:
- Operating system (you started from one)
- Language runtime (Node, Python, Ruby, JDK)
- Application dependencies (system packages, libraries)
- Monitoring agents (CloudWatch Agent, Datadog Agent, etc.)
- SSH keys for ops access (if not handled separately)
- Security baseline (CIS hardening, fail2ban, etc.)
Sometimes in the image:
- Application code (if releases are frequent, this churns the image often)
- Configuration files (if they vary per environment, hard to bake)
Never in the image:
- Secrets (use the cloud's secrets manager at runtime)
- Per-environment configuration (use cloud-init or environment variables at launch)
- Anything that changes more often than you want to rebuild the image
A common pattern: a "base image" with OS + dependencies + agents, separate from an "application image" that adds the application code. Base images change rarely (security updates, dependency updates); app images change with every deploy.
Image Layering Strategy
Three common layering strategies:
Single image. Everything baked into one image. Simple, can be slow to build (full rebuild for any change).
Base + app. A base image with infrastructure concerns (OS, agents, runtime); an app image built on top with the actual application. Faster app rebuilds.
Base + runtime + app. Three layers. Useful for highly differentiated applications that share a runtime.
For most teams: base + app is the right level of complexity. Single image is too coarse for frequent app changes; three layers is over-engineered for most.
Build Pipeline Integration
Packer should run in CI. Manual packer build from a laptop produces images nobody can reproduce later.
A typical CI workflow:
# GitHub Actions
- uses: hashicorp/setup-packer@main
- run: packer init .
- run: packer validate .
- run: packer build -only="amazon-ebs.ubuntu" template.pkr.hcl
The CI build produces an image tagged with the commit SHA and a timestamp. Auto Scaling Groups, Launch Templates, and other consumers reference the image by AMI ID.
For frequent rebuilds: cache provisioning steps that do not change. A Docker-based proxy that caches apt-get install results, for example, can dramatically speed up rebuilds.
Testing Images
A built image that does not boot is a wasted build. Test the image before promoting it:
Inside the build. Packer's goss integration runs assertions inline:
provisioner "goss" {
test_file = "goss-tests.yaml"
}
# goss-tests.yaml
service:
myapp:
enabled: true
running: true
package:
curl:
installed: true
If goss assertions fail, the build fails. The image is not produced.
After the build. Launch an instance from the new AMI, run smoke tests, then promote (tag, register in ASG, etc.) if successful.
For production-bound images: both. Inline tests catch quick issues; live tests catch boot-time problems.
Image Lifecycle
Images accumulate. A regular build cadence produces dozens or hundreds per month. Without cleanup, costs grow.
Retention patterns:
- Keep the last N versions of each image type
- Keep production-deployed versions indefinitely (need them for rollback)
- Delete dev/staging-only images after 30 days
AWS has AMI lifecycle policies; you can also script cleanup based on tags.
Image Hardening
For production: don't ship images without hardening. The minimum:
- Disable root SSH login
- Apply CIS benchmarks or similar baseline
- Install only necessary packages
- Disable services you do not use
- Apply current security patches
- Run a vulnerability scanner against the image
Tools like Lynis (open source), CIS-CAT (commercial), or AWS Inspector run against the built image and produce a hardening report.
A baked-in hardening process is one of Packer's biggest wins. Each image meets the security baseline before it ever runs.
When VM Images Are Right
Packer fits when:
- You run VMs (not just containers)
- Boot time matters (slow scaling means slow incident response)
- The deployment target supports immutable images (auto-scaling groups, instance templates)
- You can tolerate the image build time in your deploy cycle
It fits less well when:
- All workloads run in containers (use container images)
- You need to update at runtime (a hot patch on a long-lived VM)
- The build cycle is too slow for your deploy cadence
For most modern workloads: containers handle the immutable infrastructure story. VM images are for the workloads that do not fit containers — full-VM databases, GPU-heavy workloads with kernel dependencies, certain compliance-restricted environments.
Packer vs Cloud-Native Image Building
Cloud providers have their own image-building services:
- AWS Image Builder. AMI-focused. Integrates with AWS native services.
- GCP Cloud Build for VM images. Similar.
- Azure Image Builder. Similar.
These are good for single-cloud deployments. Packer is the right call for multi-cloud or when you want a tool that does not lock you in.
For most teams on a single cloud: the cloud-native tool is simpler. For multi-cloud or for teams that prefer HashiCorp tooling: Packer.
A Practical Setup
For a typical team using EC2:
- Packer template in version control, with provisioner scripts.
- CI pipeline runs Packer on push to main, producing tagged AMIs.
- goss tests inline in the Packer build to catch issues early.
- Auto Scaling Group references the latest AMI by tag.
- Lifecycle policy keeps the last 30 days of images.
Initial setup is a couple of days. Maintenance is light — Packer template updates as the system evolves.
When Packer Has Aged Out
Packer was the dominant image builder for years. Two challenges in 2026:
- Containers replaced many VM workloads. What used to be a Packer-built AMI is now a Docker image.
- Cloud-native services have matured. AWS Image Builder is competitive for AMI-only workflows.
Packer remains the right tool when:
- You build images for multiple clouds
- You have an existing investment in Packer
- You prefer HashiCorp tooling
For new single-cloud deployments: evaluate the cloud's native tool. Often simpler.
Working with VM-based infrastructure and considering image-based deployment? We help teams set up Packer pipelines that fit their build cadence and operational model. scopeforged.com