Packer, created by HashiCorp, is an open-source tool that automates the creation of identical machine images for multiple platforms from a single source configuration. Whether you need an AWS AMI, a QEMU disk image for local testing, or a VMware template, Packer lets you define the entire build in code using HCL2 templates. In this tutorial you will install Packer on RHEL 8, write HCL2 templates for both an AWS AMI and a local QEMU image, and run full build pipelines using shell and Ansible provisioners.

Prerequisites

  • RHEL 8 system with sudo privileges
  • Internet access or a configured package mirror
  • AWS account and IAM credentials with EC2 permissions (for the AMI step)
  • QEMU/KVM installed (dnf install -y qemu-kvm) for local image builds
  • Ansible installed on the build host (dnf install -y ansible)

Step 1 — Add the HashiCorp Repository and Install Packer

HashiCorp maintains an official RPM repository for RHEL-based systems. Add it with dnf config-manager and install Packer.

sudo dnf config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
sudo dnf install -y packer
packer version

Expected output: Packer v1.x.x. The binary is placed at /usr/bin/packer.

Step 2 — Write an HCL2 Template for an AWS AMI

Create a project directory and an HCL2 template. The amazon-ebs builder launches a temporary EC2 instance, provisions it, then snapshots it into a reusable AMI.

mkdir -p ~/packer-builds/aws && cd ~/packer-builds/aws

cat > aws-rhel8.pkr.hcl <= 1.0.0"
    }
  }
}

variable "region" {
  default = "us-east-1"
}

source "amazon-ebs" "rhel8" {
  ami_name      = "rhel8-golden-{{timestamp}}"
  instance_type = "t3.micro"
  region        = var.region

  source_ami_filter {
    filters = {
      name                = "RHEL-8.*_HVM-*-x86_64-*"
      root-device-type    = "ebs"
      virtualization-type = "hvm"
    }
    most_recent = true
    owners      = ["309956199498"] # Red Hat official
  }

  ssh_username = "ec2-user"
}

build {
  sources = ["source.amazon-ebs.rhel8"]

  provisioner "shell" {
    inline = [
      "sudo dnf update -y",
      "sudo dnf install -y git curl wget",
      "sudo systemctl enable --now sshd"
    ]
  }

  provisioner "ansible" {
    playbook_file = "./playbook.yml"
  }
}
EOF

Step 3 — Write an HCL2 Template for a Local QEMU Image

The qemu builder creates a raw or qcow2 disk image locally — no cloud account needed. This is ideal for testing golden images in your on-premises lab before promoting them to AWS.

mkdir -p ~/packer-builds/qemu && cd ~/packer-builds/qemu

cat > qemu-rhel8.pkr.hcl <= 1.0.0"
    }
  }
}

source "qemu" "rhel8" {
  iso_url      = "https://example.com/rhel-8.x-x86_64-boot.iso"
  iso_checksum = "sha256:CHECKSUM_HERE"
  output_directory = "output-rhel8"
  disk_size    = "20G"
  format       = "qcow2"
  accelerator  = "kvm"
  headless     = true
  http_directory = "http"
  boot_command = [
    " inst.ks=http://{{ .HTTPIP }}:{{ .HTTPPort }}/ks.cfg"
  ]
  ssh_username = "root"
  ssh_password = "packer"
  ssh_timeout  = "30m"
  shutdown_command = "shutdown -P now"
}

build {
  sources = ["source.qemu.rhel8"]

  provisioner "shell" {
    inline = [
      "dnf update -y",
      "dnf install -y python3 git"
    ]
  }
}
EOF

Step 4 — Add an Ansible Provisioner Playbook

Create the Ansible playbook referenced by the AWS template. Packer runs Ansible directly against the temporary instance over SSH.

cd ~/packer-builds/aws

cat > playbook.yml << 'EOF'
---
- hosts: all
  become: true
  tasks:
    - name: Install common packages
      dnf:
        name:
          - vim
          - htop
          - curl
          - unzip
        state: present

    - name: Set timezone
      timezone:
        name: UTC

    - name: Disable root SSH login
      lineinfile:
        path: /etc/ssh/sshd_config
        regexp: '^PermitRootLogin'
        line: 'PermitRootLogin no'
      notify: Restart sshd

  handlers:
    - name: Restart sshd
      service:
        name: sshd
        state: restarted
EOF

Step 5 — Initialize Plugins and Validate Templates

packer init downloads required plugins declared in the required_plugins block. packer validate checks HCL2 syntax and variable types without running a build.

# Initialize and validate the AWS template
cd ~/packer-builds/aws
packer init .
packer validate .

# Initialize and validate the QEMU template
cd ~/packer-builds/qemu
packer init .
packer validate .

Step 6 — Run the Builds

Set your AWS credentials as environment variables, then launch both builds. The -var flag overrides template variables at runtime without modifying the HCL2 file.

# Build the AWS AMI
export AWS_ACCESS_KEY_ID="AKIAIOSFODNN7EXAMPLE"
export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"

cd ~/packer-builds/aws
packer build -var "region=us-east-1" .

# Build the local QEMU image
cd ~/packer-builds/qemu
packer build .

# List the resulting QEMU image
ls -lh output-rhel8/

On success, Packer prints the AMI ID for the AWS build and the path to the .qcow2 file for the QEMU build.

Conclusion

You have installed Packer on RHEL 8 via the official HashiCorp RPM repository, written HCL2 templates for both an AWS AMI and a local QEMU disk image, and automated provisioning with shell commands and Ansible. Packer brings infrastructure-as-code discipline to image creation, ensuring that every environment — development, staging, and production — starts from an identical, auditable baseline. Storing your .pkr.hcl files in version control and triggering builds from a CI pipeline gives you fully automated, reproducible golden images on every commit.

Next steps: Automating AMI Promotion Across AWS Regions with Packer Post-Processors, Integrating Packer Builds into a Jenkins Pipeline on RHEL 8, and Signing and Verifying Machine Images with Cosign on RHEL 8.