When deploying applications on AWS, manually setting up instances every time can be time-consuming. Instead, we can create a pre-configured Amazon Machine Image (AMI) using Packer and Ansible, allowing us to launch instances that are ready to go with minimal setup.

This article explains how to use Packer to build an AWS AMI and Ansible to configure the instance during the build process.

Why Use Packer and Ansible?

  • Packer automates the AMI creation process, ensuring consistency and repeatability.
  • Ansible allows us to configure the AMI with required software and settings in a declarative manner.
  • Using both together helps create a golden image that is secure, optimized, and production-ready.

Repo: https://github.com/man20820/packer-ansible-nginx

Prerequisites

Before getting started, ensure you have the following:

  • An AWS account with necessary permissions to create AMIs.
  • Packer installed
  • Ansible installed
  • IAM Credentials configured via ~/.aws/credentials or environment variables.

Folder Structure

❯ tree
.
├── files
│   └── default
├── nginx.pkr.hcl
└── nginx.yaml

1 directory, 3 files

Create the Ansible Playbook

on this article, i will create nginx for example. you can customize your needs..

nginx.yaml

---
- name: Install and Configure Nginx
  hosts: all
  become: yes  # Run as sudo
  tasks:
    - name: Install Nginx on Debian-based systems
      apt:
        name: nginx
        state: present
        update_cache: yes
      when: ansible_os_family == "Debian"

    - name: Install Nginx on RHEL-based systems
      yum:
        name: nginx
        state: present
      when: ansible_os_family == "RedHat"

    - name: Start and Enable Nginx Service
      systemd:
        name: nginx
        state: started
        enabled: yes

    - name: Deploy Custom Nginx Configuration
      copy:
        src: files/default
        dest: /etc/nginx/sites-available/default
        owner: root
        group: root
        mode: '0644'
      notify: Restart Nginx

  handlers:
    - name: Restart Nginx
      systemd:
        name: nginx
        state: restarted

Create the Packer Template

packer {
  required_plugins {
    amazon = {
      source  = "github.com/hashicorp/amazon"
      version = "~> 1.2.8"
    }
    ansible = {
      version = ">= 1.1.2"
      source  = "github.com/hashicorp/ansible"
    }
  }
}

source "amazon-ebs" "custom-ami" {
  ami_name      = "nginx"
  instance_type = "t3.micro"
  region        = "ap-southeast-3"
  source_ami    = "ami-0d22ac6a0e117cefe"
  ssh_username  = "ubuntu"
  tags = {
    Name = "nginx"
  }
}

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

  provisioner "ansible" {
    playbook_file = "nginx.yaml"
    user          = "ubuntu"
  }
}

Packer Init

❯ packer init nginx.pkr.hcl
Installed plugin github.com/hashicorp/amazon v1.2.9 in "/home/man20820/.config/packer/plugins/github.com/hashicorp/amazon/packer-plugin-amazon_v1.2.9_x5.0_linux_amd64"
Installed plugin github.com/hashicorp/ansible v1.1.2 in "/home/man20820/.config/packer/plugins/github.com/hashicorp/ansible/packer-plugin-ansible_v1.1.2_x5.0_linux_amd64"

Packer Validate

❯ packer validate nginx.pkr.hcl
The configuration is valid.

Packer Build

❯ packer build nginx.pkr.hcl
amazon-ebs.custom-ami: output will be in this color.

==> amazon-ebs.custom-ami: Prevalidating any provided VPC information
==> amazon-ebs.custom-ami: Prevalidating AMI Name: nginx
    amazon-ebs.custom-ami: Found Image ID: ami-0d22ac6a0e117cefe
==> amazon-ebs.custom-ami: Creating temporary keypair: packer_67a656ad-546e-a192-4ecb-cf375be35b6a
==> amazon-ebs.custom-ami: Creating temporary security group for this instance: packer_67a656ae-a0cb-d56c-06d6-d7efe4945837
==> amazon-ebs.custom-ami: Authorizing access to port 22 from [0.0.0.0/0] in the temporary security groups...
==> amazon-ebs.custom-ami: Launching a source AWS instance...
    amazon-ebs.custom-ami: Instance ID: i-053db8a5cf0dda0ce
==> amazon-ebs.custom-ami: Waiting for instance (i-053db8a5cf0dda0ce) to become ready...
==> amazon-ebs.custom-ami: Using SSH communicator to connect: 108.137.130.187
==> amazon-ebs.custom-ami: Waiting for SSH to become available...
==> amazon-ebs.custom-ami: Connected to SSH!
==> amazon-ebs.custom-ami: Provisioning with Ansible...
    amazon-ebs.custom-ami: Setting up proxy adapter for Ansible....
==> amazon-ebs.custom-ami: Executing Ansible: ansible-playbook -e packer_build_name="custom-ami" -e packer_builder_type=amazon-ebs --ssh-extra-args '-o IdentitiesOnly=yes' -e ansible_ssh_private_key_file=/tmp/ansible-key2513666039 -i /tmp/packer-provisioner-ansible4145009209 /home/man20820/workspace/Belajar/packer/nginx.yaml
    amazon-ebs.custom-ami:
    amazon-ebs.custom-ami: PLAY [Install and Configure Nginx] *********************************************
    amazon-ebs.custom-ami:
    amazon-ebs.custom-ami: TASK [Gathering Facts] *********************************************************
    amazon-ebs.custom-ami: [WARNING]: Platform linux on host default is using the discovered Python
    amazon-ebs.custom-ami: interpreter at /usr/bin/python3.12, but future installation of another Python
    amazon-ebs.custom-ami: interpreter could change the meaning of that path. See
    amazon-ebs.custom-ami: https://docs.ansible.com/ansible-
    amazon-ebs.custom-ami: core/2.17/reference_appendices/interpreter_discovery.html for more information.
    amazon-ebs.custom-ami: ok: [default]
    amazon-ebs.custom-ami:
    amazon-ebs.custom-ami: TASK [Install Nginx on Debian-based systems] ***********************************
    amazon-ebs.custom-ami: changed: [default]
    amazon-ebs.custom-ami:
    amazon-ebs.custom-ami: TASK [Install Nginx on RHEL-based systems] *************************************
    amazon-ebs.custom-ami: skipping: [default]
    amazon-ebs.custom-ami:
    amazon-ebs.custom-ami: TASK [Start and Enable Nginx Service] ******************************************
    amazon-ebs.custom-ami: ok: [default]
    amazon-ebs.custom-ami:
    amazon-ebs.custom-ami: TASK [Deploy Custom Nginx Configuration] ***************************************
    amazon-ebs.custom-ami: changed: [default]
    amazon-ebs.custom-ami:
    amazon-ebs.custom-ami: RUNNING HANDLER [Restart Nginx] ************************************************
    amazon-ebs.custom-ami: changed: [default]
    amazon-ebs.custom-ami:
    amazon-ebs.custom-ami: PLAY RECAP *********************************************************************
    amazon-ebs.custom-ami: default                    : ok=5    changed=3    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0
    amazon-ebs.custom-ami:
==> amazon-ebs.custom-ami: Stopping the source instance...
    amazon-ebs.custom-ami: Stopping instance
==> amazon-ebs.custom-ami: Waiting for the instance to stop...
==> amazon-ebs.custom-ami: Creating AMI nginx from instance i-053db8a5cf0dda0ce
    amazon-ebs.custom-ami: AMI: ami-0d11b275bef9afba6
==> amazon-ebs.custom-ami: Waiting for AMI to become ready...
==> amazon-ebs.custom-ami: Skipping Enable AMI deprecation...
==> amazon-ebs.custom-ami: Adding tags to AMI (ami-0d11b275bef9afba6)...
==> amazon-ebs.custom-ami: Tagging snapshot: snap-0d58bc0e8cabd1cb6
==> amazon-ebs.custom-ami: Creating AMI tags
    amazon-ebs.custom-ami: Adding tag: "Name": "nginx"
==> amazon-ebs.custom-ami: Creating snapshot tags
==> amazon-ebs.custom-ami: Terminating the source AWS instance...
==> amazon-ebs.custom-ami: Cleaning up any extra volumes...
==> amazon-ebs.custom-ami: No volumes to clean up, skipping
==> amazon-ebs.custom-ami: Deleting temporary security group...
==> amazon-ebs.custom-ami: Deleting temporary keypair...
Build 'amazon-ebs.custom-ami' finished after 3 minutes 45 seconds.

==> Wait completed after 3 minutes 45 seconds

==> Builds finished. The artifacts of successful builds are:
--> amazon-ebs.custom-ami: AMIs were created:
ap-southeast-3: ami-0d11b275bef9afba6

Now we have new nginx ami…

Validate

Deploy EC2 using new ami

access the ip to test nginx configuration…

Thank you…