Skip to main content

Packer — Machine Image Builder Guide

Build identical, immutable machine images for AWS, GCP, Azure, VMware, and Docker from a single HCL2 template. HashiCorp Packer codifies your golden image pipeline.

✓ Multi-cloud builders ✓ HCL2 templates ✓ Ansible / Shell provisioners ✓ Immutable image pipelines

What is Packer?

HashiCorp Packer is an open-source tool that automates the creation of machine images across multiple platforms. Given a single template, Packer can build an AWS AMI, a GCP Image, an Azure Managed Image, and a VMware OVA simultaneously — all from the same provisioning code. Released in 2013 by Mitchell Hashimoto (co-founder of HashiCorp), Packer is a cornerstone of immutable infrastructure pipelines used by engineering teams worldwide.

The workflow is simple: Packer launches a temporary VM (or container) using a base image, runs provisioners (shell scripts, Ansible playbooks, Chef recipes) to install software and configure the OS, then captures a snapshot as an immutable image. The temporary VM is destroyed. The resulting image becomes your golden artifact — versioned, tested, and ready to deploy to auto-scaling groups or Kubernetes nodes without any post-boot configuration.

Key Features

HCL2 Template: AWS AMI with Ansible

# ubuntu-web.pkr.hcl
packer {
  required_version = ">= 1.9.0"
  required_plugins {
    amazon = {
      version = ">= 1.3.0"
      source  = "github.com/hashicorp/amazon"
    }
    ansible = {
      version = ">= 1.1.0"
      source  = "github.com/hashicorp/ansible"
    }
  }
}

variable "aws_region" {
  type    = string
  default = "us-east-1"
}

variable "ami_name_prefix" {
  type    = string
  default = "my-app"
}

variable "app_version" {
  type    = string
  description = "Application version tag to embed in the image"
}

locals {
  timestamp = regex_replace(timestamp(), "[: TZ]", "")
  ami_name  = "${var.ami_name_prefix}-${var.app_version}-${local.timestamp}"
}

source "amazon-ebs" "ubuntu" {
  region        = var.aws_region
  instance_type = "t3.medium"
  ami_name      = local.ami_name

  source_ami_filter {
    filters = {
      name                = "ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"
      root-device-type    = "ebs"
      virtualization-type = "hvm"
    }
    most_recent = true
    owners      = ["099720109477"] # Canonical
  }

  ssh_username = "ubuntu"

  tags = {
    Name        = local.ami_name
    AppVersion  = var.app_version
    BuildTime   = local.timestamp
    ManagedBy   = "Packer"
  }

  launch_block_device_mappings {
    device_name           = "/dev/sda1"
    volume_size           = 20
    volume_type           = "gp3"
    delete_on_termination = true
  }
}

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

  # Shell provisioner for initial setup
  provisioner "shell" {
    inline = [
      "sudo apt-get update -y",
      "sudo apt-get install -y python3 python3-pip",
      "sudo apt-get clean",
    ]
  }

  # Ansible provisioner for full configuration
  provisioner "ansible" {
    playbook_file   = "./ansible/site.yml"
    roles_path      = "./ansible/roles"
    extra_arguments = [
      "--extra-vars", "app_version=${var.app_version}",
      "--become",
    ]
  }

  # Write AMI ID to manifest file for downstream use
  post-processor "manifest" {
    output     = "manifest.json"
    strip_path = true
  }

  # Run local script after build (e.g., update Terraform variable)
  post-processor "shell-local" {
    inline = ["echo 'Build complete. AMI ID written to manifest.json'"]
  }
}

Validate, format, and build:

packer init .                                           # download plugins
packer fmt .                                            # format templates
packer validate -var 'app_version=1.2.3' .             # validate before build
packer build -var 'app_version=1.2.3' ubuntu-web.pkr.hcl  # run the build
cat manifest.json | jq '.builds[-1].artifact_id'       # extract AMI ID

Packer vs Alternatives

Feature Packer Docker EC2 Image Builder Manual AMI
Output formats AMI, GCP, Azure, VMware, Docker, QEMU OCI container images AWS AMI only AWS AMI only
Reproducibility Excellent (code-defined) Excellent (Dockerfile) Good Poor (manual steps)
Multi-cloud Yes Yes (registries) No (AWS only) No
Boot time Fast (pre-baked) Very fast (container) Fast Slow (userdata bootstrap)
Use case VM images, EC2 auto-scaling Containerized apps AWS-native pipelines One-off images

Use Cases

Pros and Cons

Pros

  • ✓ Codified, reproducible image builds — no more snowflake AMIs
  • ✓ Multi-platform: one template builds for multiple clouds
  • ✓ Integrates with Ansible, Chef, shell — use existing provisioning code
  • ✓ Enables immutable infrastructure and fast auto-scaling
  • ✓ packer validate catches errors before costly cloud builds
  • ✓ HCL2 format is consistent with Terraform knowledge

Cons

  • ✗ Build times can be long (10-30 min for cloud AMIs)
  • ✗ Each image update requires a full rebuild and new deploy
  • ✗ Costs money — cloud builder instances run during build
  • ✗ AMI cleanup requires separate tooling (old images accumulate)
  • ✗ Debugging failed builds requires checking cloud console logs
  • ✗ Not suitable for containerized apps (Docker is better there)

Frequently Asked Questions

What is Packer and what problem does it solve?▼

Packer is an open-source tool by HashiCorp that automates the creation of identical machine images for multiple platforms from a single configuration. Without Packer, teams create AMIs or VM images manually — clicking through console UIs, running ad-hoc scripts, and struggling to reproduce images consistently. Packer codifies the image build process: start a base VM, run provisioners (shell scripts, Ansible, Chef), capture the result as an immutable image. The same Packer template can build an AWS AMI, a VMware OVA, a Google Cloud image, and a Docker image simultaneously.

What is the difference between Packer JSON and HCL2 templates?▼

Packer originally used JSON templates (.json). Starting with Packer 1.7+, HashiCorp introduced HCL2 (.pkr.hcl) as the preferred format, aligning with Terraform's configuration language. HCL2 offers significant advantages: variables can be defined with types and validation, multiple builds are cleaner, the for_each meta-argument enables build matrices, and error messages are much more readable. New projects should use HCL2. The packer hcl2_upgrade command can automatically convert JSON templates to HCL2.

How does Packer integrate with Ansible?▼

Packer's ansible provisioner runs an Ansible playbook against the machine being built. Unlike using the shell provisioner to run ansible-playbook, Packer's ansible provisioner starts a local SSH proxy and connects to the build machine automatically — you don't need Ansible installed on the build VM. Specify the playbook_file and optional roles_path, extra_arguments, and ansible_env_vars. This is the most common pattern for teams that already use Ansible for configuration management and want the same playbooks for both ongoing config and image builds.

What is immutable infrastructure and why does Packer enable it?▼

Immutable infrastructure is the practice of never modifying running servers in place. Instead, you build a new machine image with the required changes, deploy new instances from that image, and decommission the old instances. Packer is central to this pattern because it produces versioned, tested, golden images that contain the OS, runtime, and application pre-installed. Benefits include: faster auto-scaling (no bootstrap time), consistent environments, easy rollbacks (deploy previous AMI version), and elimination of configuration drift over time.

How do I pass variables to a Packer build?▼

Packer supports several variable input methods. In HCL2, declare variables with the variable block and set defaults, types, and descriptions. Pass values via: -var 'key=value' CLI flag, -var-file=prod.pkrvars.hcl (an HCL file of key=value pairs), or PKR_VAR_key environment variables. For sensitive values like AWS credentials, use environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY) which Packer inherits from the shell — never hardcode credentials in templates.

What are Packer post-processors and when should I use them?▼

Post-processors run after builders and provisioners complete to transform or upload the artifact. Common post-processors: manifest (write build artifacts to a JSON file for use in CI/CD), compress (zip the artifact), shell-local (run a local script after build), and amazon-import (import a VMware OVA to AWS as an AMI). For multi-step pipelines, you can chain post-processors: build an image, compress it, then upload it — all in one Packer run. The manifest post-processor is especially useful for passing AMI IDs to Terraform or Ansible for deployment.

Share this tool: X / Twitter LinkedIn
✓ 60,000+ developers use DevPlaybook ★★★★★ 4.8 / 5 on Gumroad ✓ MIT licensed · Instant download ✓ No login required

Related Products

on Gumroad

You Might Also Need

More Free Tools

Enjoying this tool? Get more like it.

Weekly developer tool guides, productivity playbooks & AI tricks. Free. No spam.

DevPlaybook Pro 7 days free

Unlock AI-Powered Dev Tools

  • ⚡ AI Code Review, Doc Generator & SQL Builder
  • ⚡ All premium templates & early access
  • ⚡ Member discounts on Gumroad products
Start Free Trial →

$9/mo after trial · Cancel anytime

Want the full toolkit?

Get DevPlaybook Pro

Every template, guide, boilerplate, and automation script in one bundle. 13 premium products — grab them all at once and save big.

$79
$241 separately Save $162 (67% off)
Get DevPlaybook Pro — $79 →

MIT licensed · Instant download · No subscription

See what's included → Browse all deals →