HashiCorp Packer is an open-source tool for creating identical machine images for multiple platforms from a single configuration source. Rather than manually configuring servers after deployment, Packer bakes your configuration — packages, users, services, certificates — directly into the image at build time, embodying the immutable infrastructure pattern. The resulting AMIs, OVA files, or container images are tested artifacts that can be deployed predictably across environments. This guide covers installing Packer on RHEL 9 and building both cloud and local images using HCL2 templates.
Prerequisites
- RHEL 9 system with
sudoprivileges - An AWS account with IAM credentials configured (for the AMI example)
- QEMU or VirtualBox installed for local image builds
- Basic familiarity with shell scripting or Ansible
Step 1 — Install Packer from the HashiCorp Repository
# Add the HashiCorp YUM repository
sudo tee /etc/yum.repos.d/hashicorp.repo > /dev/null <<EOF
[hashicorp]
name=HashiCorp Stable - $basearch
baseurl=https://rpm.releases.hashicorp.com/RHEL/$releasever/$basearch/stable
enabled=1
gpgcheck=1
gpgkey=https://rpm.releases.hashicorp.com/gpg
EOF
# Install Packer
sudo dnf install -y packer
# Verify the installation
packer version
# On some systems, packer conflicts with a cracklib utility; create an alias if needed
which packer
# If it points to /usr/sbin/packer (cracklib), use the full path /usr/bin/packer
Step 2 — Understand the HCL2 Template Structure
# A Packer HCL2 template has three main blocks:
# 1. packer {} — required plugins and version constraints
# 2. source {} — where and how to build the machine (builder)
# 3. build {} — what to install and configure (provisioners)
# Create a project directory
mkdir ~/packer-demo && cd ~/packer-demo
# Example minimal template structure (do not run yet — see next steps)
# packer {
# required_plugins { ... }
# }
#
# source "amazon-ebs" "my_ami" {
# ami_name = "my-image-{{timestamp}}"
# ...
# }
#
# build {
# sources = ["source.amazon-ebs.my_ami"]
# provisioner "shell" { ... }
# }
Step 3 — Build an AWS AMI with the amazon-ebs Builder
# Configure AWS credentials first
export AWS_ACCESS_KEY_ID="YOUR_ACCESS_KEY"
export AWS_SECRET_ACCESS_KEY="YOUR_SECRET_KEY"
export AWS_DEFAULT_REGION="us-east-1"
# Create the Packer template
cat > aws-ami.pkr.hcl <= 1.3.0"
source = "github.com/hashicorp/amazon"
}
}
}
source "amazon-ebs" "rhel9_base" {
ami_name = "rhel9-base-{{timestamp}}"
instance_type = "t3.micro"
region = "us-east-1"
source_ami_filter {
filters = {
name = "RHEL-9*GA*"
root-device-type = "ebs"
virtualization-type = "hvm"
}
most_recent = true
owners = ["309956199498"] # Red Hat's AWS account ID
}
ssh_username = "ec2-user"
}
build {
sources = ["source.amazon-ebs.rhel9_base"]
provisioner "shell" {
inline = [
"sudo dnf update -y",
"sudo dnf install -y nginx git curl",
"sudo systemctl enable nginx"
]
}
}
EOF
# Initialize plugins, validate, then build
packer init aws-ami.pkr.hcl
packer validate aws-ami.pkr.hcl
packer build aws-ami.pkr.hcl
Step 4 — Build a Local QEMU Image
# Install QEMU
sudo dnf install -y qemu-kvm qemu-img
cat > local-qemu.pkr.hcl <= 1.1.0"
source = "github.com/hashicorp/qemu"
}
}
}
source "qemu" "rhel9_local" {
iso_url = "https://mirror.example.com/rhel-9.4-x86_64-dvd.iso"
iso_checksum = "sha256:YOUR_ISO_CHECKSUM"
output_directory = "output-rhel9-qemu"
disk_size = "20G"
memory = 2048
cpus = 2
boot_wait = "10s"
ssh_username = "root"
ssh_password = "packer"
shutdown_command = "shutdown -P now"
}
build {
sources = ["source.qemu.rhel9_local"]
provisioner "shell" {
script = "scripts/provision.sh"
}
}
EOF
packer init local-qemu.pkr.hcl
packer validate local-qemu.pkr.hcl
packer build local-qemu.pkr.hcl
Step 5 — Use Ansible as a Provisioner
# Install Ansible on the Packer host
sudo dnf install -y ansible-core
# Add an Ansible provisioner block inside your build {}
cat >> aws-ami.pkr.hcl < playbooks/configure.yml <<'EOF'
---
- hosts: all
become: true
tasks:
- name: Install packages
ansible.builtin.dnf:
name: [nginx, git, curl]
state: present
- name: Enable nginx
ansible.builtin.systemd:
name: nginx
enabled: true
EOF
# Validate the updated template
packer validate aws-ami.pkr.hcl
Conclusion
With Packer installed on RHEL 9 and HCL2 templates in place, you can automate the creation of golden images for AWS, GCP, Azure, or local hypervisors from the same codebase. Adopting the immutable infrastructure pattern means deployments become image swaps rather than configuration drift risks — every server launched from your AMI or OVA is identical and tested. Integrate packer build into your CI/CD pipeline so a new base image is produced automatically whenever your provisioning scripts or playbooks change.
Next steps: How to Set Up Vagrant and VirtualBox on RHEL 9, How to Configure Terraform for AWS Infrastructure on RHEL 9, and How to Automate RHEL 9 Configuration with Ansible Playbooks.