Skip to content
Learni
View all tutorials
DevOps

How to Create AWS AMIs with Packer in 2026

Lire en français

Introduction

Packer, HashiCorp's open-source tool, lets you create identical machine images for multiple platforms like AWS, Azure, or VirtualBox. In 2026, with the rise of infrastructure as code (IaC), Packer is essential for standardizing production environments, reducing configuration drift, and accelerating deployments.

Imagine: instead of manually configuring an Ubuntu EC2 instance every time, you generate a ready-to-use AMI with Nginx, your Node.js app, and basic security measures in a single command. This eliminates human error and ensures reproducibility.

This intermediate tutorial is for DevOps familiar with AWS. We'll build an Ubuntu 22.04 AMI with a shell provisioner to install a basic LEMP stack. Result: an immutable, tested, auto-tagged image. Estimated time: 20 min. Have your AWS credentials ready! (128 words)

Prerequisites

  • Packer 1.11+ installed (download from hashicorp/packer)
  • AWS account with IAM permissions: AmazonEC2FullAccess, AmazonEBSFullAccess
  • AWS CLI v2 configured (aws configure with access key/secret)
  • Environment variables: export AWS_ACCESS_KEY_ID=... and AWS_SECRET_ACCESS_KEY=...
  • AWS region us-east-1 (customizable)
  • Basic knowledge of HCL and Linux shell

Installing Packer

install-packer.sh
#!/bin/bash

# Download the latest stable version (1.11.2 in 2026)
PACKER_VERSION="1.11.2"
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
ARCH=$(uname -m)

curl -fsSL "https://releases.hashicorp.com/packer/${PACKER_VERSION}/packer_${PACKER_VERSION}_${OS}_${ARCH}.zip" -o packer.zip

unzip -o packer.zip
sudo mv packer /usr/local/bin/

# Verification
packer version

rm packer.zip packer

This script downloads, installs, and verifies Packer in one go. Use sudo for the global executable. Pitfall: Check the architecture (amd64/arm64); on M1 Mac, use arm64. Add export PATH=/usr/local/bin:$PATH if needed.

First Steps: Validating Your Environment

Before coding, test your AWS setup. Packer uses credentials from the AWS CLI or env vars. Create a project folder: mkdir packer-ami && cd packer-ami.

Analogy: Packer is like a cake mold: you define the recipe (HCL), and it produces baked images ready to scale. Next: variables to parameterize region, source AMI, etc. This makes the template reusable across environments (dev/prod).

Packer Variables File

variables.pkr.hcl
aws_region     = "us-east-1"
ubuntu_ami     = "ami-0c02fb55956c7d316"  # Ubuntu 22.04 LTS us-east-1 (vérifiez avec aws ec2 describe-images)
instance_type  = "t3.micro"
ssh_username   = "ubuntu"
ssh_timeout    = "10m"
ami_name       = "ubuntu-lemp-${formatdate("YYYYMMDD-HHmm")}"  # Timestamp unique
ami_tags = {
  Name        = "Ubuntu LEMP Packer"
  Environment = "production"
  ManagedBy   = "Packer"
}

This file defines dynamic inputs. formatdate ensures unique AMI names. Pitfall: Update ubuntu_ami via AWS Console > EC2 > Public AMIs. Use locals for complex derived values later.

Basic Packer Configuration

ubuntu.pkr.hcl
packer {
  required_plugins {
    amazon = {
      version = ">= 1.1.1"
      source  = "github.com/hashicorp/amazon"
    }
  }
  required_version = ">= 1.7.0"
}

source "amazon-ebs" "ubuntu" {
  access_key    = ""
  secret_key    = ""
  region        = var.aws_region
  ami_name      = var.ami_name
  instance_type = var.instance_type
  source_ami    = var.ubuntu_ami
  ssh_username  = var.ssh_username
  ssh_timeout   = var.ssh_timeout
  tags = var.ami_tags
  vpc_filter {
    filters = {
      "tag:Name" : "Default VPC"
    }
  }
  subnet_filter {
    random = true
    filters = {
      "tag:Name" : "Default subnet"
    }
  }
}

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

Minimal template to build a custom AMI from Ubuntu. vpc_filter uses your Default VPC; random=true for subnet. Pitfall: Without explicit credentials, Packer reads AWS env vars. Run packer init . before building.

Validation and First Build

Run packer init . to download the Amazon plugin. Then packer validate . to check HCL syntax. If OK, launch the build: it spins up a temporary EC2, snapshots the EBS volume, and publishes the AMI.

Tip: Monitor logs for debugging (e.g., SSH timeouts). The final AMI appears in EC2 Console > Images > AMIs owned by me. Time: 5-10 min.

Building the Base AMI

build-base.sh
#!/bin/bash

# Initialize plugins
packer init .

# Validate HCL
packer validate .

# Build (uncomment -debug for step-by-step)
packer build -var-file="variables.pkr.hcl" .

# List created AMIs (optional)
aws ec2 describe-images --owners self --filters "Name=name,Values=*ubuntu-lemp*" --query 'Images[*].[ImageId,Name,CreationDate]' --output table

Complete script to validate and build. -var-file loads variables. Add --only=amazon-ebs.ubuntu for specific builds. Pitfall: If VPC not found, create one or hardcode subnet_id.

Adding a Shell Provisioner

ubuntu.pkr.hcl
packer {
  required_plugins {
    amazon = {
      version = ">= 1.1.1"
      source  = "github.com/hashicorp/amazon"
    }
  }
  required_version = ">= 1.7.0"
}

source "amazon-ebs" "ubuntu" {
  access_key    = ""
  secret_key    = ""
  region        = var.aws_region
  ami_name      = var.ami_name
  instance_type = var.instance_type
  source_ami    = var.ubuntu_ami
  ssh_username  = var.ssh_username
  ssh_timeout   = var.ssh_timeout
  tags = var.ami_tags
  vpc_filter {
    filters = {
      "tag:Name" : "Default VPC"
    }
  }
  subnet_filter {
    random = true
    filters = {
      "tag:Name" : "Default subnet"
    }
  }
}

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

  provisioner "shell" {
    inline = [
      "sudo apt-get update",
      "sudo apt-get install -y nginx mysql-server php8.2-fpm",
      "sudo systemctl enable nginx mysql",
      "echo '<h1>LEMP Packer AMI OK</h1>' | sudo tee /var/www/html/index.html",
      "sudo mysql_secure_installation <<EOF\nY\ny\nroot\nY\nY\nY\nY\nEOF"
    ]
    timeout = "10m"
  }
}

Shell provisioner installs LEMP (Linux, Nginx, MySQL, PHP) after boot. inline runs sequential commands. Pitfall: mysql_secure_installation is interactive; use heredoc to automate. Timeout prevents hangs.

Building with Provisioner

build-lemp.sh
#!/bin/bash

packer init .
packer validate .

# Build with force to recreate (deletes old AMIs if needed)
packer build -force -var-file="variables.pkr.hcl" .

# Test: Launch an instance from the AMI and curl localhost

-force recreates the AMI without name conflicts. Post-build, test by launching an EC2. Pitfall: AWS costs ~$0.02/build; clean up with aws ec2 deregister-image and delete snapshots.

Best Practices

  • Use variables and locals: Centralize creds/secrets in auto.pkrvars.hcl (gitignore!) and locals { timestamp = formatdate... } for dynamism.
  • Multi-builders: Add source "virtualbox-iso" {} for local testing before cloud.
  • Idempotent provisioners: Prefer Ansible/Chef over raw shell for replayability.
  • Post-processors: post-processor "manifest" {} for JSON output; compress to pack the AMI.
  • CI/CD: Integrate with GitHub Actions/Terraform Cloud for automated builds on push.

Common Errors to Avoid

  • SSH Timeout: Increase ssh_timeout to 20m; check Security Group (SSH port 22 open).
  • Outdated Source AMI: Query AWS: aws ec2 describe-images --filters 'Name=name,Values=ubuntu/images/hvm-ssd/ubuntu-jammy-22.04*' --owners 099720109477.
  • Insufficient IAM Permissions: Attach policy EC2InstanceProfileForImageBuilder.
  • VPC Drift: Hardcode subnet_id or create a dedicated VPC for Packer.

Next Steps

Master advanced builders (Azure, GCP) and custom builders. Check the official Packer docs. Integrate with Terraform via data.aws_ami. Explore our DevOps training at Learni for Packer + Terraform in production.