Packer â Automated Machine Image Building Guide
In this tutorial, you'll learn about Packer. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
Packer is an open-source tool by HashiCorp for creating identical machine images for multiple platforms from a single source template, automating the entire image build process with infrastructure-as-code practices.
What You'll Learn
Why It Matters
Manually creating machine images (golden AMIs, VM templates) is slow, inconsistent, and untraceable. Packer automates the process â you define the source OS, install software, apply configurations, and produce a ready-to-use image. DodaTech uses Packer to build golden AMIs for AWS EC2 instances, baking security agents, monitoring tools, and application dependencies into every image.
Real-World Use
DodaZIP's CI pipeline triggers a Packer build on every release. Packer starts a fresh Ubuntu 24.04 EC2 instance, installs the Durga Antivirus Pro agent, Node.js runtime, application code, monitoring stack (Node Exporter, Filebeat), and security hardening â then creates an AMI. Auto Scaling groups launch instances from this golden AMI, eliminating configuration drift entirely.
flowchart LR
A[Packer Template] --> B[Source: ubuntu-24-04]
A --> C[Provisioners]
A --> D[Post-Processors]
B --> E[Builder: amazon-ebs]
C --> F[shell: install packages]
C --> G[ansible: configure app]
C --> H[file: copy artifacts]
D --> I[Create AMI]
D --> J[Share with accounts]
D --> K[Cleanup builder]
I --> L[AWS EC2 Launch]
style A fill:#02A8EF,color:#fff
Prerequisites: Terraform or infrastructure-as-code familiarity. Cloud provider account (AWS, GCP, Azure).
Installation
# Install Packer on Ubuntu
wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | sudo tee /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
# Verify
packer --version
# Expected output:
# Packer v1.11.0
# Install via script
curl -fsSL https://releases.hashicorp.com/packer/1.11.0/packer_1.11.0_linux_amd64.zip -o packer.zip
unzip packer.zip
sudo mv packer /usr/local/bin/
HCL2 Template Structure
# dodazip-ami.pkr.hcl
packer {
required_plugins {
amazon = {
version = ">= 1.3.0"
source = "github.com/hashicorp/amazon"
}
ansible = {
version = ">= 1.1.0"
source = "github.com/hashicorp/ansible"
}
}
}
variable "ami_name" {
type = string
default = "dodazip-golden-ami"
}
variable "instance_type" {
type = string
default = "t3.medium"
}
variable "region" {
type = string
default = "us-east-1"
}
locals {
timestamp = formatdate("YYYYMMDDhhmmss", timestamp())
ami_name = "${var.ami_name}-${local.timestamp}"
}
source "amazon-ebs" "golden" {
ami_name = local.ami_name
instance_type = var.instance_type
region = var.region
source_ami_filter {
filters = {
name = "ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*"
root-device-type = "ebs"
virtualization-type = "hvm"
}
most_recent = true
owners = ["099720109477"]
}
ssh_username = "ubuntu"
launch_block_device_mappings {
device_name = "/dev/sda1"
volume_size = 30
volume_type = "gp3"
delete_on_termination = true
}
tags = {
Name = local.ami_name
Environment = "production"
ManagedBy = "packer"
CreatedBy = "dodatech-platform"
BuildDate = local.timestamp
}
run_tags = {
Name = "packer-builder-${local.timestamp}"
Environment = "production"
ManagedBy = "packer"
}
run_volume_tags = {
Name = "packer-builder-${local.timestamp}"
ManagedBy = "packer"
}
snapshot_tags = {
Name = local.ami_name
Environment = "production"
ManagedBy = "packer"
}
}
build {
name = "dodazip-golden"
sources = ["source.amazon-ebs.golden"]
provisioner "shell" {
inline = [
"echo '=== Updating system packages ==='",
"sudo apt-get update -qq",
"sudo apt-get upgrade -y -qq",
"echo '=== Installing system dependencies ==='",
"sudo apt-get install -y -qq curl wget gnupg ca-certificates lsb-release",
]
}
provisioner "file" {
source = "provisioning/install-monitoring.sh"
destination = "/tmp/install-monitoring.sh"
}
provisioner "shell" {
script = "provisioning/install-monitoring.sh"
}
provisioner "ansible" {
playbook_file = "provisioning/playbook.yml"
ansible_env_vars = [
"ANSIBLE_HOST_KEY_CHECKING=False]
]
}
provisioner "shell" {
inline = [
"echo '=== Cleaning up for smaller AMI ==='",
"sudo apt-get autoremove -y -qq",
"sudo apt-get clean -qq",
"sudo rm -rf /var/log/*.log /tmp/* /var/tmp/*",
"echo '=== Build complete ==='",
]
}
post-processor "manifest" {
output = "manifest.json"
strip_path = true
}
}
Building the Image
# Format and validate the template
packer fmt dodazip-ami.pkr.hcl
packer validate dodazip-ami.pkr.hcl
# Expected output:
# The configuration is valid.
# Build the AMI
packer build dodazip-ami.pkr.hcl
# Expected output:
# amazon-ebs.golden: output will be in this color.
# Build 'amazon-ebs.golden' running.
# amazon-ebs.golden: Creating temporary keypair for this instance...
# amazon-ebs.golden: Creating temporary security group for this instance...
# amazon-ebs.golden: Launching a source AWS instance...
# amazon-ebs.golden: Instance ID: i-0123456789abcdef0
# amazon-ebs.golden: Waiting for SSH to become available...
# amazon-ebs.golden: Connected to SSH!
# amazon-ebs.golden: Provisioning with shell script...
# amazon-ebs.golden: === Updating system packages ===
# amazon-ebs.golden: === Installing system dependencies ===
# amazon-ebs.golden: Provisioning with Ansible...
# amazon-ebs.golden: === Installing Durga Antivirus ===
# amazon-ebs.golden: === Installing Node Exporter ===
# amazon-ebs.golden: === Cleaning up for smaller AMI ===
# amazon-ebs.golden: Stopping instance...
# amazon-ebs.golden: Creating AMI dodazip-golden-20260624120000...
# amazon-ebs.golden: AMI: ami-0a1b2c3d4e5f67890
# amazon-ebs.golden: Cleaning up resources...
# Build 'amazon-ebs.golden' finished.
# ==> Builds finished. The artifacts of successful builds are:
# --> amazon-ebs.golden: AMIs were created:
# us-east-1: ami-0a1b2c3d4e5f67890
GCP Image Builder
# dodazip-gcp.pkr.hcl
packer {
required_plugins {
googlecompute = {
version = ">= 1.1.0"
source = "github.com/hashicorp/googlecompute"
}
}
}
source "googlecompute" "golden" {
project_id = "dodatech-platform"
source_image = "ubuntu-2404-lts-amd64"
zone = "us-central1-a"
image_name = "dodazip-golden-${local.timestamp}"
image_family = "dodazip-golden"
ssh_username = "ubuntu"
machine_type = "e2-medium"
disk_size = 30
disk_type = "pd-ssd"
tags = ["packer-builder"]
}
build {
sources = ["source.googlecompute.golden"]
provisioner "shell" {
inline = [
"sudo apt-get update -qq",
"sudo apt-get install -y -qq nginx nodejs durga-antivirus-pro",
"sudo systemctl enable nginx",
]
}
provisioner "file" {
source = "configs/"
destination = "/opt/dodazip/"
}
}
Variables and Auto-Encoding
# variables.pkr.hcl
variable "aws_access_key" {
type = string
sensitive = true
}
variable "aws_secret_key" {
type = string
sensitive = true
}
variable "source_ami" {
type = string
default = null
}
variable "build_version" {
type = string
default = "latest"
}
# Pass variables
packer build \
-var 'aws_access_key=AKIA...' \
-var 'aws_secret_key=...' \
-var 'build_version=2.5.0' \
dodazip-ami.pkr.hcl
# Use variable file
cat dodazip.auto.pkrvars.hcl
aws_access_key = "AKIA..."
aws_secret_key = "..."
build_version = "2.5.0"
region = "us-west-2"
CI/CD Integration
# .github/workflows/packer-build.yml
name: Build Golden AMI
on:
push:
branches: [main]
paths:
- 'packer/**'
- 'provisioning/**'
jobs:
packer:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Packer
uses: hashicorp/setup-packer@v3
with:
version: latest
- name: Validate template
run: packer validate packer/dodazip-ami.pkr.hcl
- name: Build AMI
run: |
packer build \
-var "aws_access_key=${{ secrets.AWS_ACCESS_KEY }}" \
-var "aws_secret_key=${{ secrets.AWS_SECRET_KEY }}" \
-var "build_version=${{ github.sha }}" \
packer/dodazip-ami.pkr.hcl
Common Configuration Mistakes
Not specifying
source_ami_filtercorrectly: Wrong filters produce incompatible base images. Always setownersto the official Canonical (099720109477) or Microsoft publisher IDs.Forgetting cleanup in provisioners: Packer creates a snapshot of the entire filesystem. Without
apt-get cleanand log removal, AMIs are unnecessarily large (5-10GB extra).Using
ssh_private_key_filewithout proper permissions: Packer generates ephemeral SSH keys by default. Using custom keys requireschmod 600on the key file.Not tagging builder instances: Without
run_tags, AWS shows orphaned instances during builds. Tags help identify and clean up failed builder instances.Hardcoding secrets in templates: Use variables marked
sensitive = trueand pass via environment variables or--varflags. Never commit secrets to version control.
Practice Questions
What is a Packer builder? Answer: A builder creates a machine instance for a specific platform (AWS EC2, GCP Compute, Azure VM, Docker, VMware). It launches the source image, provisions it, and creates the output artifact.
How do provisioners differ in Packer? Answer: Provisioners customize the image:
shellruns scripts,fileuploads files,<a href="/devops/ansible/">ansible</a>runs playbooks,chefruns cookbooks,<a href="/microsoft-technologies/powershell/">PowerShell</a>runs PowerShell scripts.What is a post-processor? Answer: Post-processors run after the image is created â extracting the manifest, compressing the image, uploading to artifact storage, or triggering downstream pipelines.
How does Packer ensure idempotent builds? Answer: Packer always starts from a clean source image and applies provisioners fresh. There is no incremental state â each build produces identical output from the same source and provisioners.
Challenge
Create a Packer pipeline for a golden machine image: write an HCL2 template for AWS AMI (Ubuntu 24.04), install and configure NGINX, Node.js 22, Prometheus Node Exporter, Filebeat, and Durga Antivirus Pro, apply security hardening (SSH config, firewall rules, fail2ban), create the AMI with proper tags, share it with specified AWS accounts, store the manifest as a CI artifact, and trigger a Terraform workspace update to roll out the new AMI to Auto Scaling groups.
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro