Packer, when building AWS AMIs, doesn’t just bake a static image; it orchestrates a complex, ephemeral dance of AWS resources that are spun up and torn down, leaving behind only the precious AMI.
Let’s watch it in action. Imagine you have a simple Packer template to build a basic Ubuntu AMI with nginx installed.
{
"variables": {
"aws_region": "us-east-1",
"source_ami_filter_name": "ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"
},
"builders": [
{
"type": "amazon-ebs",
"region": "{{user `aws_region`}}",
"source_ami_filter_type": "aws-marketplace",
"source_ami_filter_name": "{{user `source_ami_filter_name`}}",
"instance_type": "t2.micro",
"ssh_username": "ubuntu",
"ami_name": "my-nginx-ami-{{timestamp}}",
"tags": {
"Name": "my-nginx-ami",
"BuildTool": "Packer"
}
}
],
"provisioners": [
{
"type": "shell",
"inline": [
"sudo apt-get update",
"sudo apt-get install -y nginx"
]
}
]
}
When you run packer build your-template.json, here’s what’s happening under the hood:
- Temporary EC2 Instance: Packer requests a temporary EC2 instance in your specified
regionusing theinstance_type(t2.microhere). It selects asource_amithat matches yoursource_ami_filter_name. This instance is not your final AMI; it’s the staging ground. - SSH Connection: Packer uses the
ssh_username(ubuntu) and your configured AWS keys (usually via environment variables or assh_keypair_namein the builder) to establish an SSH connection to this temporary instance. - Provisioning: The
provisionersblock defines the steps to customize the instance. In this case, it runsapt-get updateand installsnginxviashellcommands. Packer streams these commands over SSH. - Snapshotting and AMI Creation: Once provisioning is complete and successful, Packer tells AWS to stop the temporary instance. It then creates a snapshot of the instance’s root volume. This snapshot becomes the basis for your new AMI. Packer then registers this snapshot as a new AMI with a name like
my-nginx-ami-1678886400. - Cleanup: Crucially, Packer then terminates the temporary EC2 instance and deletes any associated EBS volumes that were created specifically for this build. The only artifact that persists is the newly created AMI.
The problem Packer solves is the repeatable, automated creation of machine images. Instead of manually launching an EC2 instance, configuring it, and then creating an AMI, Packer codifies this entire process. This is vital for immutable infrastructure, where you replace servers rather than patching them in place.
The mental model is that Packer is a remote execution engine. It spins up a temporary compute environment (an EC2 instance), runs your configuration scripts on it, captures the state of that environment into a shareable artifact (an AMI), and then cleans up the temporary environment.
The source_ami_filter_type and source_ami_filter_name are powerful. You can filter by exact AMI IDs, by names using wildcards, or even by AMIs owned by specific AWS accounts (like the official aws-marketplace or aws-canonical for Ubuntu). This allows you to start from a known good base image and build upon it.
The tags applied to the builder are applied to the temporary EC2 instance during its life, and importantly, also to the final AMI once it’s created. This is how you can track which AMIs were built by Packer and with what configuration.
The real magic happens when you combine this with other AWS services. For instance, you might use Packer to build an AMI for a specific application, then use AWS Systems Manager (SSM) or EC2 Auto Scaling to deploy instances from that AMI.
Packer’s builders abstract away the underlying infrastructure. You tell it what you want (an AMI with Nginx) and Packer figures out how to get it on AWS. It handles the temporary EC2 instance creation, SSH connectivity, and the AMI baking process, all based on your JSON or HCL configuration.
The ami_name format string, especially {{timestamp}}, is your best friend for ensuring unique AMI names with each build, preventing collisions and making it easy to identify the most recent build.
It’s important to remember that the instance_type specified in the builder is for the temporary instance Packer uses for building, not for the instances that will eventually run from your AMI. You can use a small, cheap instance for building and then deploy much larger instances from the resulting AMI.
The next concept you’ll likely encounter is managing multiple builders within a single Packer template, allowing you to build AMIs for different regions or instance architectures simultaneously.