Building ARM AMIs for Graviton2 instances with Packer is surprisingly straightforward once you understand how AWS handles ARM architecture builds and how Packer orchestrates them.
Let’s see it in action. Imagine we have a simple Packer template to build an Ubuntu 22.04 AMI for a1 (Graviton2) instances.
packer {
required_plugins {
amazon = {
version = ">= 1.0.0"
source = "github.com/hashicorp/amazon"
}
}
}
variable "aws_region" {
type = string
default = "us-east-1"
}
variable "instance_type" {
type = string
default = "t4g.micro" # Graviton2 instance type
}
source "amazon-ebs" "ubuntu_graviton2" {
region = var.aws_region
instance_type = var.instance_type
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 owner ID
most_recent = true
}
ssh_username = "ubuntu"
ami_name = "ubuntu-graviton2-{{timestamp}}"
}
build {
sources = ["source.amazon-ebs.ubuntu_graviton2"]
provisioner "shell" {
inline = [
"sudo apt-get update",
"sudo apt-get install -y nginx",
"sudo systemctl enable nginx"
]
}
}
When you run packer build your-template.pkr.hcl, Packer will:
- Find a suitable ARM64 AMI: It looks for an AMI that matches
ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*owned by099720109477. Crucially, because theinstance_typeist4g.micro, Packer (via the AWS provider) implicitly knows to search for an ARM64 variant of this Ubuntu AMI. AWS’s AMI catalog is intelligent enough to offer architecture-specific AMIs when you request them. - Launch an EC2 instance: It will launch an EC2 instance of type
t4g.microusing the discovered ARM64 AMI. - Connect via SSH: It establishes an SSH connection to this instance using the
ubuntuuser. - Run provisioners: It executes the
apt-getandnginxinstallation commands. - Create the AMI: Once provisioning is complete, it stops the instance and creates a new AMI from its root volume.
The core problem this solves is creating consistent, reproducible machine images for AWS Graviton2 (ARM64) instances. Traditionally, building custom AMIs could be a manual, error-prone process. Packer automates this, allowing you to define your desired OS, software, and configurations in code.
Internally, Packer leverages the AWS API. For ARM builds, the key is specifying an instance_type that is ARM-based (like t4g.micro, m6g.large, c6g.xlarge, etc.). The amazon-ebs builder then queries AWS for AMIs compatible with that architecture. If you were to specify an x86_64 instance type, it would naturally find an x86_64 AMI. The source_ami_filter can be used to be more explicit, but often, letting the instance_type guide the AMI selection is sufficient.
The source_ami_filter’s name field can sometimes be tricky. While ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-* might look like it’s only for AMD64, AWS’s AMI lookup mechanism is smart enough to find the ARM64 equivalent if the requested instance_type is ARM. If you needed to be absolutely explicit and found a specific ARM64 AMI ID, you could use source_ami = "ami-xxxxxxxxxxxxxxxxx" and then the instance_type wouldn’t be as critical for AMI discovery. However, using source_ami_filter is generally preferred for portability.
A subtle point is that while the name filter might contain amd64, this is often a convention in the AMI name itself from the publisher (Canonical, in this case). The underlying AWS infrastructure correctly maps the ARM64 instance_type request to an ARM64 AMI that satisfies the other filter criteria (like OS version, virtualization type, etc.). You don’t typically need to hunt for an arm64 string in the AMI name filter itself if you’re using a common OS like Ubuntu or Amazon Linux 2.
When you run packer build, the amazon-ebs builder will fetch the source_ami_filter results. If it finds multiple AMIs, it applies the most_recent = true filter. If it finds an AMI for the wrong architecture (x86_64 when you wanted ARM64), it means the source_ami_filter didn’t narrow it down enough, and the instance launch failed because the requested instance_type (t4g.micro) is incompatible with the selected AMI’s architecture. In such a case, you’d need to refine your source_ami_filter or, more commonly, ensure your source_ami_filter is broad enough that AWS can pick the correct architecture based on the instance_type.
The next hurdle you’ll likely encounter is managing different ARM distributions or specific kernel requirements for more advanced use cases.