Packer

Packer builds machine images from a template. It launches a temporary machine from a base image, runs provisioners to install and configure software, captures the result as a new image, and deletes the machine. The same template can produce an AWS AMI, an Azure image, a GCP image and a local VM image.

It is the tool behind immutable infrastructure for virtual machines: servers are never patched in place, they are replaced with ones built from a newer image. See infrastructure as code for the wider picture.

Bake or fry

Bake (build an image ahead of time)Fry (configure at boot)
Boot timeSeconds — everything is already installedMinutes of package installs and configuration
ReliabilityNo dependency on package mirrors at scale-out timeA mirror outage breaks autoscaling
ConsistencyEvery instance is byte-identicalInstances built on different days differ
RollbackLaunch the previous imageReverse the configuration, hopefully
Change costA build per changeEdit a script

The usual compromise bakes everything slow and stable — OS patches, agents, runtimes, hardening — and leaves only environment-specific values for boot time through user data or cloud-init. Fast boot matters most where capacity is added under load: EC2 auto scaling groups and Kubernetes nodes.

A template

# web.pkr.hcl
packer {
  required_plugins {
    amazon  = { source = "github.com/hashicorp/amazon",  version = "~> 1.3" }
    ansible = { source = "github.com/hashicorp/ansible", version = "~> 1.1" }
  }
}
 
variable "version" { type = string }
 
source "amazon-ebs" "web" {
  region        = "eu-west-1"
  instance_type = "t4g.small"
  ssh_username  = "ec2-user"
  ami_name      = "web-${var.version}-{{timestamp}}"
 
  source_ami_filter {
    owners      = ["amazon"]
    most_recent = true
    filters = {
      name         = "al2023-ami-2023.*-arm64"
      architecture = "arm64"
    }
  }
 
  encrypt_boot = true
  imds_support = "v2.0"
 
  tags = {
    Name       = "web"
    version    = var.version
    base_ami   = "{{ .SourceAMI }}"
    built_by   = "packer"
  }
}
 
build {
  sources = ["source.amazon-ebs.web"]
 
  provisioner "shell" {
    inline = ["sudo dnf -y update"]
  }
 
  provisioner "ansible" {
    playbook_file = "playbooks/web.yaml"
  }
 
  provisioner "shell" {
    script = "scripts/cleanup.sh" # remove keys, logs, caches, machine-id
  }
 
  post-processor "manifest" {
    output = "manifest.json"
  }
}
packer init .
packer fmt -check . && packer validate -var version=1.8.0 .
packer build -var version=1.8.0 .
ConceptRole
Source / builderWhere and how to launch the temporary machine: amazon-ebs, azure-arm, googlecompute, qemu, docker
ProvisionerWhat to do on it: shell, ansible, file, powershell
Post-processorWhat to do with the result: write a manifest, compress, upload, sign
PluginBuilders and provisioners are distributed as versioned plugins — pin them

Reusing Ansible roles as the provisioner means the same code configures long-lived servers and bakes images.

What belongs in an image

Bake in: OS security updates; monitoring, logging and security agents; language runtimes; hardening (CIS benchmarks, sysctl settings); organisation CA certificates.

Leave out: secrets of any kind, environment-specific configuration, SSH host keys and authorized_keys, the machine ID, shell history and logs. An image is copied to many machines and often shared across accounts — anything in it should be treated as public within the organisation. The clean-up script at the end of the build exists for this.

An image pipeline

base image published ─┐
template changed ─────┼─► build ─► test ─► scan ─► tag "candidate" ─► deploy to staging
weekly schedule ──────┘                                                     │
                                                                  soak, then promote
                                                                            │
                                         share to prod accounts ◄── tag "approved"
                                                    │
                                    rolling replacement of instances
  1. Trigger on template changes, on a new base image, and on a schedule — a weekly rebuild is how images stay patched.
  2. Test the built image: boot it, check services, ports and versions with Goss, InSpec or Testinfra.
  3. Scan for vulnerabilities (Inspector, Trivy) and fail on criticals.
  4. Promote by tag, not by rebuilding. The image tested in staging is the image that reaches production.
  5. Roll out by updating the launch template and performing an instance refresh.
  6. Expire old images and their snapshots; they cost storage and accumulate vulnerabilities.

Record the base image, commit and build number as tags so any running instance can be traced back to its source. This is the VM equivalent of the practices in container image scanning and pipeline design.

Images and Terraform

Packer creates the image; Terraform uses it. Connect them through a data source rather than pasting IDs:

data "aws_ami" "web" {
  owners      = ["self"]
  most_recent = true
  filter {
    name   = "tag:Name"
    values = ["web"]
  }
  filter {
    name   = "tag:status"
    values = ["approved"]
  }
}

A new approved image then shows up as a planned change to the launch template. See Terraform.

Machine images, containers and Kubernetes nodes

Machine imageContainer image
ContainsA whole OS including the kernelApplication and user-space libraries only
Built withPacker, EC2 Image BuilderA Dockerfile or buildpacks
SizeGigabytesMegabytes
Starts inTens of secondsUnder a second
Use forHosts and nodesApplications

In a Kubernetes shop most applications ship as containers (images and layers), and Packer’s job shrinks to the node image. On EKS, AWS publishes the node AMI build as Packer templates in the amazon-eks-ami repository; you would fork it only for requirements the stock image cannot meet — mandated agents, a specific kernel, pre-pulled large images. Otherwise prefer the managed AMIs or Bottlerocket and customise through user data. See managed node group basics.

Alternatives

  • EC2 Image Builder — AWS-managed pipelines with built-in testing, scheduling and cross-account distribution; AWS only.
  • Azure VM Image Builder — a managed service that itself runs Packer.
  • Bottlerocket, Flatcar, Talos — purpose-built immutable container hosts where there is little left to bake.

Like Terraform, Packer is distributed under the Business Source License; the plugin ecosystem remains open.

Further reading