Packer is an open-source tool by HashiCorp that automates the creation of machine images for multiple platforms from a single source template. Instead of manually configuring a server, taking a snapshot, and hoping you documented every step, Packer codifies the entire process. You write a template, run packer build, and get identical images for AWS, Azure, Docker, and VMware — all from one definition.
This guide covers Packer installation, HCL template syntax, builders, provisioners, multi-platform builds, and integrating Packer into CI/CD pipelines for automated image delivery.
Prerequisites
- Packer installed (v1.9+)
- An account on at least one target platform (AWS, Azure, Docker, or VirtualBox)
- AWS CLI configured (if building AMIs)
- Basic understanding of machine images and server provisioning
- Familiarity with HCL syntax (similar to Terraform)
Installing Packer
Install from the HashiCorp repository for automatic updates:
# Ubuntu/Debian
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install packer
# macOS
brew install packer
# Verify installation
packer version
Your First Packer Template
Packer uses HCL (HashiCorp Configuration Language) templates. Here is a minimal example that builds a Docker image:
# docker-nginx.pkr.hcl
packer {
required_plugins {
docker = {
version = ">= 1.0.0"
source = "github.com/hashicorp/docker"
}
}
}
source "docker" "nginx" {
image = "ubuntu:22.04"
commit = true
}
build {
name = "custom-nginx"
sources = ["source.docker.nginx"]
provisioner "shell" {
inline = [
"apt-get update",
"apt-get install -y nginx curl",
"rm -rf /var/lib/apt/lists/*"
]
}
post-processor "docker-tag" {
repository = "myorg/custom-nginx"
tags = ["latest", "1.0"]
}
}
Build it:
# Initialize plugins
packer init docker-nginx.pkr.hcl
# Validate the template
packer validate docker-nginx.pkr.hcl
# Build the image
packer build docker-nginx.pkr.hcl
Building AWS AMIs with Packer
The most common Packer use case is building Amazon Machine Images (AMIs):
# aws-webserver.pkr.hcl
packer {
required_plugins {
amazon = {
version = ">= 1.2.0"
source = "github.com/hashicorp/amazon"
}
}
}
variable "aws_region" {
type = string
default = "us-east-1"
}
variable "app_version" {
type = string
default = "1.0.0"
}
source "amazon-ebs" "webserver" {
ami_name = "webserver-{{timestamp}}"
instance_type = "t3.micro"
region = var.aws_region
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"
tags = {
Name = "WebServer"
Version = var.app_version
Builder = "Packer"
Environment = "production"
}
}
build {
sources = ["source.amazon-ebs.webserver"]
provisioner "shell" {
inline = [
"sudo apt-get update",
"sudo apt-get install -y nginx nodejs npm certbot",
"sudo systemctl enable nginx"
]
}
provisioner "file" {
source = "config/nginx.conf"
destination = "/tmp/nginx.conf"
}
provisioner "shell" {
inline = [
"sudo cp /tmp/nginx.conf /etc/nginx/nginx.conf",
"sudo nginx -t",
"sudo systemctl restart nginx"
]
}
}
# Build with variable overrides
packer build -var "aws_region=eu-west-1" -var "app_version=2.1.0" aws-webserver.pkr.hcl
Provisioner Types
Packer supports multiple provisioners that run inside the temporary build instance:
Shell Provisioner
provisioner "shell" {
scripts = [
"scripts/base-setup.sh",
"scripts/install-app.sh",
"scripts/harden.sh"
]
environment_vars = [
"APP_ENV=production",
"DEBIAN_FRONTEND=noninteractive"
]
}
Ansible Provisioner
provisioner "ansible" {
playbook_file = "ansible/playbook.yml"
extra_arguments = [
"--extra-vars", "app_version=${var.app_version}",
"--tags", "setup,deploy"
]
ansible_env_vars = [
"ANSIBLE_HOST_KEY_CHECKING=False"
]
}
File Provisioner
provisioner "file" {
source = "app/dist/"
destination = "/opt/app/"
}
Real-world scenario: Your company runs 50 EC2 instances behind a load balancer. Each time you deploy, you build a new AMI with Packer containing the latest application code, Nginx configuration, and security patches. Terraform then performs a rolling update — launching new instances with the fresh AMI and draining old ones. If the new image has issues, you roll back to the previous AMI in minutes. No SSH-ing into servers, no configuration drift, no snowflake servers.
Multi-Platform Builds
Build images for multiple platforms from one template:
source "amazon-ebs" "aws" {
ami_name = "myapp-aws-{{timestamp}}"
instance_type = "t3.micro"
region = "us-east-1"
source_ami_filter {
filters = { name = "ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*" }
owners = ["099720109477"]
most_recent = true
}
ssh_username = "ubuntu"
}
source "docker" "container" {
image = "ubuntu:22.04"
commit = true
}
source "virtualbox-iso" "local" {
iso_url = "https://releases.ubuntu.com/22.04/ubuntu-22.04.3-live-server-amd64.iso"
iso_checksum = "sha256:abcdef..."
ssh_username = "vagrant"
ssh_password = "vagrant"
# ... additional VirtualBox config
}
build {
sources = [
"source.amazon-ebs.aws",
"source.docker.container",
]
provisioner "shell" {
inline = [
"sudo apt-get update",
"sudo apt-get install -y nginx",
]
}
}
Run packer build . and Packer builds both images in parallel.
Packer vs Alternatives Comparison
| Feature | Packer | Dockerfile | AWS Image Builder | Ansible (images) |
|---|---|---|---|---|
| Multi-platform | Yes (any cloud + local) | Docker only | AWS only | Yes (with plugins) |
| Config Language | HCL | Dockerfile syntax | JSON/YAML | YAML |
| Layer Caching | No (full rebuild) | Yes (layers) | Partial | No |
| Build Speed | Minutes | Seconds-minutes | Minutes | Minutes |
| Provisioner Support | Shell, Ansible, Chef, Puppet | RUN commands | SSM/AWSTOE | Native |
| CI/CD Integration | Excellent | Excellent | AWS-only | Good |
| Immutable Output | Yes (snapshot) | Yes (image) | Yes (AMI) | Depends |
| Learning Curve | Medium | Low | Medium | Medium |
When to choose Packer: Multi-cloud environments, VM-based deployments, golden image pipelines, or when you need the same image on AWS and VMware simultaneously.
Gotchas and Edge Cases
-
Stale source AMI filters: If your
source_ami_filtermatches multiple AMIs,most_recent = trueensures you get the latest. Without it, Packer picks arbitrarily and your builds become non-deterministic. -
Build timeouts: Packer waits for SSH connectivity after launching the build instance. Slow cloud regions or restrictive security groups cause timeouts. Increase
ssh_timeoutfrom the default 5 minutes if needed. -
Provisioner ordering matters: Provisioners run in the order they appear in the template. A
fileprovisioner that copies to/opt/app/will fail if the directory does not exist yet — add ashellprovisioner first to create it. -
IAM permissions for AWS builds: Packer needs
ec2:RunInstances,ec2:CreateImage,ec2:DescribeImages, and several more. Use the minimal Packer IAM policy rather than giving it full admin access. -
Deregistering old AMIs: Packer creates new AMIs but never deletes old ones. Without cleanup, you accumulate hundreds of unused AMIs. Use a lifecycle policy or a scheduled script to deregister images older than N days.
-
Disk space during builds: Temporary build instances use the default root volume size. If your provisioning installs many packages, the disk fills up. Add
launch_block_device_mappingsto increase the root volume.
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
| ”Timeout waiting for SSH” | Security group blocks SSH or instance slow to boot | Check SG allows port 22 from builder IP; increase ssh_timeout |
| ”AccessDenied” during AMI creation | IAM policy missing ec2:CreateImage | Add required EC2 and EBS permissions to IAM role |
| Provisioner scripts fail | Script runs before packages update | Add apt-get update as first step; use DEBIAN_FRONTEND=noninteractive |
| Build succeeds but AMI missing | Wrong region or account | Check region in source block; use aws ec2 describe-images to verify |
| Packer build slow | No parallel builds configured | Use -parallel-builds=N or separate source blocks for parallelism |
Summary
- Packer automates machine image creation from code, eliminating manual server configuration and ensuring reproducible builds
- HCL templates define sources (where to build), provisioners (what to install), and post-processors (what to do with the output)
- Multi-platform builds produce identical images for AWS, Docker, and VMware from a single template
- Ansible provisioner integrates existing configuration management into Packer builds for complex setups
- CI/CD integration enables automatic image builds on every infrastructure change, creating an immutable deployment pipeline
- Pair with Terraform for a complete workflow — Packer builds the image, Terraform deploys it