
What is Packer?
Packer, developed by HashiCorp, is a tool designed to create identical machine images for multiple platforms from a single configuration file. It supports various builders (e.g., AWS, Azure, Google Cloud) and allows you to define the image-building process using JSON or HCL (HashiCorp Configuration Language). For AWS, Packer uses the amazon-ebs builder to create AMIs backed by Elastic Block Store (EBS).
Key Features of Packer
- Multi-Platform Support: Build images for AWS, Azure, GCP, Docker, and more.
- Immutable Infrastructure: Create consistent, reproducible images for predictable deployments.
- Extensibility: Use provisioners like Ansible, Chef, or shell scripts to customize images.
- Automation: Integrate with CI/CD pipelines for automated image creation.
- Idempotency: Ensure images are built the same way every time, reducing configuration drift.
Packer’s ability to automate AMI creation makes it an essential tool for modern cloud workflows, especially when combined with Infrastructure as Code (IaC) practices.
Why Use Packer for AWS AMIs?
AWS AMIs are the foundation of EC2 instances, defining the operating system, application stack, and configurations. While you can manually create AMIs using the AWS Management Console, this approach is error-prone, time-consuming, and not scalable. Packer addresses these challenges by:
- Standardizing AMIs: Ensure all instances launched from an AMI have the same configuration.
- Reducing Manual Effort: Automate the installation of software, patches, and dependencies.
- Enabling Versioning: Create versioned AMIs to track changes and roll back if needed.
- Improving Security: Apply security patches and harden images during the build process.
- Supporting CI/CD: Integrate with tools like Jenkins or GitHub Actions for continuous image updates.
By using Packer, you can codify the AMI creation process, making it repeatable and auditable. This is particularly valuable for organizations adopting DevOps practices.
Prerequisites
Before we start building an AMI with Packer, ensure you have the following:
- AWS Account: An active AWS account with permissions to create EC2 instances, AMIs, and IAM roles.
- AWS CLI: Installed and configured with credentials (aws configure).
- Packer: Installed on your local machine or a CI/CD environment. Download Packer from the official website or use a package manager like Homebrew (brew install packer).
- Basic Knowledge: Familiarity with AWS EC2, AMIs, and JSON/HCL syntax.
- Text Editor: A code editor like VS Code for writing Packer templates.
- SSH Key Pair: An EC2 key pair for accessing instances during the build process.
Optional but recommended:
- IAM Role: Create an IAM role with permissions for Packer to interact with AWS services.
- Version Control: Use Git to manage Packer templates and scripts.
With these prerequisites in place, you’re ready to start building AMIs with Packer.
Setting Up Packer for AWS
Step 1: Install Packer
Download and install Packer based on your operating system. For example, on macOS:
brew install packer
Verify the installation:
packer --version
This should display the installed Packer version (e.g., 1.8.6).
Step 2: Configure AWS Credentials
Packer needs AWS credentials to interact with your account. You can provide credentials in one of the following ways:
- AWS CLI Configuration: If you’ve configured the AWS CLI, Packer will use the credentials from ~/.aws/credentials.
- Environment Variables: Set AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and optionally AWS_SESSION_TOKEN.
- IAM Instance Profile: Use an IAM role attached to the EC2 instance running Packer (recommended for CI/CD).
For simplicity, we’ll assume you’ve configured the AWS CLI. Run aws configure and provide your Access Key ID, Secret Access Key, region (e.g., us-east-1), and output format (e.g., json).
Step 3: Create a Packer Template
Packer templates define the image-building process. You can write templates in JSON or HCL. While JSON was the standard for older versions, HCL is preferred for its readability and support in newer Packer versions. We’ll use HCL for this guide.
Create a file named ami.pkr.hcl with the following structure:
packer {
required_plugins {
amazon = {
version = ">= 1.0.0"
source = "github.com/hashicorp/amazon"
}
}
}
source "amazon-ebs" "ubuntu" {
ami_name = "custom-ubuntu-ami-{{timestamp}}"
instance_type = "t2.micro"
region = "us-east-1"
source_ami_filter {
filters = {
name = "ubuntu/images/*ubuntu-focal-20.04-amd64-server-*"
root-device-type = "ebs"
virtualization-type = "hvm"
}
most_recent = true
owners = ["099720109477"] # Canonical
}
ssh_username = "ubuntu"
}
build {
sources = ["source.amazon-ebs.ubuntu"]
provisioner "shell" {
inline = [
"sudo apt-get update",
"sudo apt-get install -y nginx",
"sudo systemctl enable nginx",
"sudo systemctl start nginx"
]
}
}
- Packer Block: Specifies required plugins, including the amazon plugin for AWS.
- Source Block: Defines the amazon-ebs builder, which creates an EBS-backed AMI.
- ami_name: The name of the resulting AMI, with a timestamp for uniqueness.
- instance_type: The EC2 instance type used during the build (e.g., t2.micro).
- region: The AWS region where the AMI will be created.
- source_ami_filter: Selects the base AMI (e.g., Ubuntu 20.04 from Canonical).
- ssh_username: The default SSH user for the base AMI (e.g., ubuntu for Ubuntu AMIs).
- Build Block: Specifies the sources and provisioners.
- provisioner “shell”: Runs shell commands to install and configure Nginx on the instance.
This template creates an AMI based on Ubuntu 20.04 with Nginx installed.
Step 4: Validate the Template
Before building the AMI, validate the template for syntax errors:
bashCollapseWrapRunCopy
packer validate ami.pkr.hcl
If no errors are reported, you’re ready to build the AMI.
Step 5: Build the AMI
Run the following command to start the build process:
bashCollapseWrapRunCopy
packer build ami.pkr.hcl
Packer will:
- Launch an EC2 instance based on the source AMI.
- Connect to the instance via SSH.
- Execute the provisioner scripts (e.g., install Nginx).
- Create an AMI from the instance.
- Terminate the instance.
You’ll see logs in the terminal, and upon completion, Packer will output the AMI ID (e.g., ami-1234567890abcdef0).
Testing the AMI
To verify the AMI, launch an EC2 instance using the AWS Management Console or CLI:
bashCollapseWrapRunCopy
aws ec2 run-instances \
--image-id ami-1234567890abcdef0 \
--instance-type t2.micro \
--key-name your-key-pair \
--region us-east-1
SSH into the instance and check if Nginx is running:
bashCollapseWrapRunCopy
curl http://localhost
You should see the default Nginx welcome page. This confirms the AMI was built correctly.
Advanced Packer Configurations
Now that you’ve built a basic AMI, let’s explore advanced configurations to enhance your Packer workflow.
Using Variables
Hardcoding values in the template can make it less reusable. Packer supports variables to parameterize configurations. Update ami.pkr.hcl to include variables:
packer {
required_plugins {
amazon = {
version = ">= 1.0.0"
source = "github.com/hashicorp/amazon"
}
}
}
variable "region" {
type = string
default = "us-east-1"
}
variable "instance_type" {
type = string
default = "t2.micro"
}
variable "ami_prefix" {
type = string
default = "custom-ubuntu-ami"
}
source "amazon-ebs" "ubuntu" {
ami_name = "${var.ami_prefix}-{{timestamp}}"
instance_type = var.instance_type
region = var.region
source_ami_filter {
filters = {
name = "ubuntu/images/*ubuntu-focal-20.04-amd64-server-*"
root-device-type = "ebs"
virtualization-type = "hvm"
}
most_recent = true
owners = ["099720109477"]
}
ssh_username = "ubuntu"
}
build {
sources = ["source.amazon-ebs.ubuntu"]
provisioner "shell" {
inline = [
"sudo apt-get update",
"sudo apt-get install -y nginx",
"sudo systemctl enable nginx",
"sudo systemctl start nginx"
]
}
}
You can override variables via the command line:
bashCollapseWrapRunCopy
packer build -var "region=us-west-2" -var "instance_type=t3.micro" ami.pkr.hcl
Using Provisioners
Packer supports multiple provisioners to customize the image. For example, you can use Ansible to apply complex configurations. Add an Ansible provisioner to the build block:
hclCollapseWrapCopy
build {
sources = ["source.amazon-ebs.ubuntu"]
provisioner "ansible" {
playbook_file = "./playbook.yml"
extra_arguments = ["--extra-vars", "ansible_python_interpreter=/usr/bin/python3"]
}
}
Create a playbook.yml file with Ansible tasks:
- hosts: all
become: yes
tasks:
- name: Install Nginx
apt:
name: nginx
state: present
update_cache: yes
- name: Ensure Nginx is running
service:
name: nginx
state: started
enabled: yes
This Ansible playbook achieves the same result as the shell provisioner but is more maintainable for complex configurations.
Tagging AMIs
Tags help organize and identify AMIs. Add tags to the source block:
hclCollapseWrapCopy
source "amazon-ebs" "ubuntu" {
ami_name = "${var.ami_prefix}-{{timestamp}}"
instance_type = var.instance_type
region = var.region
source_ami_filter {
filters = {
name = "ubuntu/images/*ubuntu-focal-20.04-amd64-server-*"
root-device-type = "ebs"
virtualization-type = "hvm"
}
most_recent = true
owners = ["099720109477"]
}
ssh_username = "ubuntu"
tags = {
Name = "CustomUbuntuAMI"
Environment = "Production"
BuiltBy = "Packer"
}
}
Sharing AMIs
To share AMIs with other AWS accounts or make them public, use the ami_users or ami_groups options:
hclCollapseWrapCopy
source "amazon-ebs" "ubuntu" {
ami_name = "${var.ami_prefix}-{{timestamp}}"
instance_type = var.instance_type
region = var.region
source_ami_filter {
filters = {
name = "ubuntu/images/*ubuntu-focal-20.04-amd64-server-*"
root-device-type = "ebs"
virtualization-type = "hvm"
}
most_recent = true
owners = ["099720109477"]
}
ssh_username = "ubuntu"
ami_users = ["123456789012"] # Share with another AWS account
ami_groups = ["all"] # Make the AMI public
}
Security Best Practices
To create secure AMIs:
- Use a Secure Base AMI: Start with a trusted AMI from AWS Marketplace or a reputable provider like Canonical.
- Apply Security Patches: Update packages during the build process (apt-get update && apt-get upgrade -y).
- Harden the OS: Disable root login, configure firewalls (e.g., ufw), and remove unnecessary software.
- Use Secrets Management: Avoid hardcoding credentials in templates. Use AWS Secrets Manager or environment variables.
- Restrict SSH Access: Use a temporary SSH key pair and remove it after the build.
Integrating Packer with CI/CD
To automate AMI creation, integrate Packer with a CI/CD pipeline. Here’s an example using GitHub Actions:
- Create a GitHub repository with your Packer template and scripts.
- Add a workflow file (e.g., .github/workflows/build-ami.yml):
name: Build AMI with Packer
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Packer
uses: hashicorp/setup-packer@v2
with:
packer_version: 1.8.6
- name: Validate Packer template
run: packer validate ami.pkr.hcl
- name: Build AMI
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: packer build ami.pkr.hcl
- Store AWS credentials as GitHub Secrets (AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY).
- Push changes to the main branch to trigger the workflow.
This pipeline validates and builds the AMI whenever code is pushed, ensuring your AMIs stay up to date.
Building AWS AMIs with Packer is a powerful way to automate and standardize your cloud infrastructure. By defining image creation as code, Packer enables reproducible, secure, and efficient AMI workflows. In this guide, we covered the basics of Packer, walked through creating a custom Ubuntu AMI with Nginx, and explored advanced features like variables, provisioners, and CI/CD integration.
Whether you’re a DevOps engineer or a cloud enthusiast, mastering Packer will enhance your ability to manage AWS environments effectively. Experiment with different provisioners, integrate with your CI/CD pipeline, and explore Packer’s extensive documentation to unlock its full potential.
💬 Comments
No comments yet. Be the first to share your thoughts!
📝 Leave a Comment