Packer doesn’t build immutable infrastructure; it enables it by creating versioned, identical artifacts that are never modified after creation.

Let’s see what that looks like. Imagine you’re building a standard Ubuntu 22.04 AMI for AWS. You’ve got a Packer template (ubuntu.pkr.hcl):

source "amazon-ebs" "ubuntu" {

  ami_name      = "ubuntu-2204-{{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's Ubuntu AMI ID
  }
  ssh_username = "ubuntu"
  tags = {
    "OS"    = "Ubuntu",
    "Ver"   = "22.04",

    "Build" = "{{timestamp}}"

  }
}

build {
  sources = ["source.amazon-ebs.ubuntu"]

  provisioner "shell" {
    inline = [
      "sudo apt-get update",
      "sudo DEBIAN_FRONTEND=noninteractive apt-get upgrade -y",
      "sudo DEBIAN_FRONTEND=noninteractive apt-get install -y nginx",
      "sudo systemctl enable nginx",

      "echo 'Hello from Packer build {{timestamp}}' | sudo tee /var/www/html/index.html"

    ]
  }
}

When you run packer init . and then packer build ubuntu.pkr.hcl, Packer spins up a temporary EC2 instance, bootstraps it with the specified Ubuntu AMI, runs your shell provisioners (updating packages, installing Nginx, and creating a simple index.html), and then creates a new AMI from that configured instance. This new AMI is tagged with a unique timestamp, like ubuntu-22.04-20231027100000.

The magic is that this AMI is now a golden image. It’s a complete, self-contained unit with everything you need – the OS, Nginx, its configuration – baked in. You don’t patch this AMI. You don’t log into this AMI to make changes. Instead, you use this AMI as the source for a new build.

This solves the problem of configuration drift. In traditional mutable infrastructure, servers are updated over time. Patches are applied, software is upgraded, configurations are tweaked. Over weeks or months, two servers that started identically can become wildly different, leading to subtle bugs and debugging nightmares. Immutable infrastructure, enabled by Packer golden images, means you replace servers rather than updating them.

When you need to update Nginx or apply OS patches, you modify your Packer template (e.g., change the nginx install command or add a new apt-get upgrade step), re-run packer build, and get a new AMI with a new timestamp. Then, your deployment system (like Terraform, Ansible, or a CI/CD pipeline) launches new instances from this new AMI, replacing the old ones.

The entire mental model shifts from "how do I update this running server?" to "how do I build a better server image and deploy it?"

The ami_name pattern ubuntu-22.04-{{timestamp}} is crucial. The {{timestamp}} variable injects the current date and time in YYYYMMDDHHMMSS format, ensuring each AMI is uniquely named. This is how you track and version your images. When you need to roll back, you simply deploy instances from an older, known-good AMI.

Most people don’t realize how much you can control the exact state of the image. The source_ami_filter is incredibly powerful. You can pinpoint specific OS versions, architectures, and even filter by AMI owner. This allows you to reliably start from a known base, preventing surprises from upstream changes to the base AMIs themselves.

The next step is integrating these golden images into a full deployment pipeline, automating the build and subsequent launch of instances from the new images.

Want structured learning?

Take the full Packer course →