Creating a Reusable Amazon EBS AMI with Packer
Step‑by‑step guide to define, validate, and build an Amazon‑EBS‑backed AMI using Packer, with verification and cleanup instructions.
06 Mar 2026, 07:19 UTC

Desired outcome
Produce a reproducible Amazon Machine Image (AMI) that contains a specific set of software (e.g., Python 3 and nginx) and can be launched repeatedly in any AWS region.
Prerequisites
- Packer version 1.8 or later installed locally or in a CI runner.
- An AWS account with access key and secret key that allow the IAM principal to perform EC2 actions:
RunInstances,CreateImage,DescribeImages,DescribeSnapshots,DeleteSnapshot, andDeregisterImage. - AWS CLI configured (optional but useful for verification).
- SSH key pair that will be used to connect to the temporary build instance (the private key must be accessible to the Packer process).
- Basic familiarity with HCL2 or JSON syntax for Packer templates.
Focused procedure
1. Define variables
Create a file variables.pkr.hcl (or variables.json) that holds values you do not want to hard‑code:
variable "aws_access_key" {}
variable "aws_secret_key" {}
variable "region" { default = "us-east-1" }
variable "instance_type" { default = "t3.micro" }
variable "ssh_username" { default = "ec2-user" }
variable "source_ami_filter" {
type = map(string)
default = {
owners = "amazon"
most_recent = true
name = "amzn2-ami-hvm-*-x86_64-gp2"
}
}
Provide the secret values via environment variables when running Packer, e.g.:
export AWS_ACCESS_KEY_ID=$(cat .aws_access_key)
export AWS_SECRET_ACCESS_KEY=$(cat .aws_secret_key)
packer build -var-file=variables.pkr.hcl template.pkr.hcl
2. Write the Packer template
Save the following as template.pkr.hcl. It uses the amazon-ebs builder with an AMI filter to pick the latest Amazon Linux 2 AMI in the chosen region, then installs Python 3 and nginx via a shell provisioner.
packer {
required_plugins {
amazon = { version = ">= 1.0.0", source = "github.com/hashicorp/amazon" }
}
}
source "amazon-ebs" "base" {
access_key = var.aws_access_key
secret_key = var.aws_secret_key
region = var.region
instance_type = var.instance_type
ssh_username = var.ssh_username
ami_filter {
owners = var.source_ami_filter["owners"]
most_recent = true
names = [var.source_ami_filter["name"]]
}
ami_name = "packer-base-{{timestamp}}"
tags = {
Purpose = "packer-demo"
CreatedBy = "packer"
}
}
build {
sources = ["source.amazon-ebs.base"]
provisioner "shell" {
inline = [
"sudo yum update -y",
"sudo amazon-linux-extras install nginx1 -y",
"sudo yum install -y python3",
"sudo systemctl enable nginx",
"sudo systemctl start nginx"
]
}
}
3. Initialize, validate, and build
- Initialize plugins (downloads the amazon‑ebs plugin if not present):
packer init template.pkr.hcl
- Check the template syntax:
packer validate template.pkr.hcl
- Run the build (this creates a temporary EC2 instance, runs provisioners, and registers an AMI):
packer build template.pkr.hcl
During the build, Packer prints lines like:
==> amazon-ebs.base: artifact,0,id=us-east-1:ami-0a1b2c3d4e5f6g7h8
The value after id= is the newly created AMI ID.
Expected checks
1. Verify AMI existence and state
Using the AWS CLI (replace ami-0a1b2c3d4e5f6g7h8 with the ID from the build output):
aws ec2 describe-images --image-ids ami-0a1b2c3d4e5f6g7h8 --query 'Images[0].{State:State,Tags:Tags}' --output table
You should see State = available and the tags you defined (Purpose=packer-demo, CreatedBy=packer).
2. Validate provisioned software
Launch a temporary instance from the AMI and test SSH access:
aws ec2 run-instances \
--image-id ami-0a1b2c3d4e5f6g7h8 \
--instance-type t3.micro \
--key-name MyKeypair \
--subnet-id subnet-xxxxxxxx \
--security-group-ids sg-xxxxxxxx \
--associate-public-ip-address \
--query 'Instances[0].InstanceId' --output text
Wait for the instance to reach running state, retrieve its public IP, then:
ssh -i ~/.ssh/MyKeypair.pem ec2-user@<public-ip> "which python3 && nginx -v"
Expect a path to python3 and the nginx version string.
Recovery / rollback options
Because the build creates an AMI and an associated snapshot, you can clean up if the image is not needed:
- Deregister the AMI:
aws ec2 deregister-image --image-id ami-0a1b2c3d4e5f6g7h8
- Delete the snapshot(s) linked to the AMI (describe the AMI first to get snapshot IDs):
aws ec2 describe-images --image-ids ami-0a1b2c3d4e5f6g7h8 --query 'Images[0].BlockDeviceMappings[*].Ebs.SnapshotId' --output text
For each snapshot ID returned, run:
aws ec2 delete-snapshot --snapshot-id snap-xxxxxxxx
Note: Deregistering the AMI does not automatically delete snapshots; they must be removed separately to avoid storage charges.
Limitations and practical tips
- The
amazon-ebs builder creates a temporary instance in the specified region; ensure your VPC and subnet allow outbound internet access for package updates. - If you need to reuse the same AMI across multiple regions, copy it after build using
aws ec2 copy-image. - Avoid placing AWS secret keys directly in the template; the example uses variables sourced from environment variables or a secrets manager.
- For production pipelines, consider adding a post‑process step that runs
packer inspectto confirm the artifact metadata before promoting the AMI.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.