Building Custom Ubuntu AMIs with Packer and Ansible Link to heading
In today’s cloud-native world, having consistent and automated infrastructure deployment is crucial. One of the key components in this process is creating custom Amazon Machine Images (AMIs) that are pre-configured with all necessary software and settings. In this post, we’ll explore how to build custom Ubuntu AMIs using Packer and Ansible, two powerful tools in the DevOps ecosystem.
Why Custom AMIs? Link to heading
Custom AMIs offer several advantages:
- Consistent environment across deployments
- Faster instance launch times
- Pre-installed software and configurations
- Security hardening out of the box
- Reduced configuration drift
Project Overview Link to heading
Our project builds an AWS AMI using Packer and configures it with Ansible. The resulting AMI is based on Ubuntu 22.04 LTS and includes:
- System updates and security patches
- User management (creates an jenkins user with sudo access)
- Common system packages and security tools
Prerequisites Link to heading
Before we begin, ensure you have:
- AWS account with appropriate permissions
- Packer installed
- Ansible installed
- AWS CLI configured with credentials
- SSH public key at ~/.ssh/id_rsa.pub
Project Structure Link to heading
packer-ansible/
├── ubuntu-ami.pkr.hcl # Packer template
├── ansible/
│ ├── playbook.yml # Main Ansible playbook
│ └── roles/
│ ├── system_updates/ # System update role
│ │ └── tasks/
│ │ └── main.yml
│ ├── user_management/ # User management role
│ │ └── tasks/
│ │ └── main.yml
│ └── package_installation/ # Package installation role
│ └── tasks/
│ └── main.yml
Packer Configuration Link to heading
The Packer template (ubuntu-ami.pkr.hcl) defines our build process:
packer {
required_plugins {
amazon = {
version = ">= 1.2.8"
source = "github.com/hashicorp/amazon"
}
}
}
variable "aws_region" {
type = string
default = "us-east-1"
}
variable "source_ami" {
type = string
default = "ami-0c7217cdde317cfec" # Ubuntu 22.04 LTS
}
source "amazon-ebs" "ubuntu" {
ami_name = "ubuntu-22-04-ansible-{{timestamp}}"
instance_type = "t2.micro"
region = var.aws_region
source_ami = var.source_ami
ssh_username = "ubuntu"
tags = {
Name = "Ubuntu 22.04 with Ansible"
}
}
build {
sources = ["source.amazon-ebs.ubuntu"]
provisioner "ansible" {
playbook_file = "./ansible/playbook.yml"
extra_arguments = [
"--extra-vars", "ansible_python_interpreter=/usr/bin/python3"
]
}
}
Ansible Configuration Link to heading
The Ansible playbook (playbook.yml) orchestrates the configuration:
---
- name: Configure Ubuntu 22.04 AMI
hosts: all
become: true
roles:
- system_updates
- user_management
- package_installation
The playbook uses 3 simple roles:
system_updates: Handles system updates and security patchesuser_management: Creates and configures jenkins userspackage_installation: Installs common system packages
Let’s look at the main tasks for each role:
# system_updates/tasks/main.yml
---
- name: Update apt package index
apt:
update_cache: yes
cache_valid_time: 3600
- name: Upgrade all packages
apt:
upgrade: dist
force_apt_get: yes
- name: Install security updates
apt:
upgrade: yes
update_cache: yes
cache_valid_time: 3600
# user_management/tasks/main.yml
---
- name: Create jenkins user
user:
name: jenkins
groups: sudo
shell: /bin/bash
state: present
create_home: yes
- name: Set up authorized key for jenkins user
authorized_key:
user: jenkins
state: present
key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
- name: Allow sudo without password
lineinfile:
path: /etc/sudoers
line: "jenkins ALL=(ALL) NOPASSWD:ALL"
validate: 'visudo -cf %s'
# package_installation/tasks/main.yml
---
- name: Install common system packages
apt:
name:
- htop
- vim
- curl
- wget
- git
- unzip
- python3-pip
state: present
update_cache: yes
Building the AMI Link to heading
To build the AMI:
packer build ubuntu-ami.pkr.hcl
amazon-ebs.ubuntu: output will be in this color.
==> amazon-ebs.ubuntu: Prevalidating any provided VPC information
==> amazon-ebs.ubuntu: Prevalidating AMI Name: ubuntu-22-04-ansible-1744517631
amazon-ebs.ubuntu: Found Image ID: ami-0c7217cdde317cfec
==> amazon-ebs.ubuntu: Creating temporary keypair: packer_67fb39ff-3511-3eef-3ff1-297fb3c3b3c9
==> amazon-ebs.ubuntu: Creating temporary security group for this instance: packer_67fb3a01-ac73-b9e3-c24f-12ac4428be83
==> amazon-ebs.ubuntu: Authorizing access to port 22 from [0.0.0.0/0] in the temporary security groups...
==> amazon-ebs.ubuntu: Launching a source AWS instance...
amazon-ebs.ubuntu: Instance ID: i-082d0913508638938
==> amazon-ebs.ubuntu: Waiting for instance (i-082d0913508638938) to become ready...
==> amazon-ebs.ubuntu: Using SSH communicator to connect: 3.87.68.167
==> amazon-ebs.ubuntu: Waiting for SSH to become available...
==> amazon-ebs.ubuntu: Connected to SSH!
==> amazon-ebs.ubuntu: Provisioning with Ansible...
amazon-ebs.ubuntu: Setting up proxy adapter for Ansible....
==> amazon-ebs.ubuntu: Executing Ansible: ansible-playbook -e packer_build_name="ubuntu" -e packer_builder_type=amazon-ebs --ssh-extra-args '-o IdentitiesOnly=yes' --extra-vars ansible_python_interpreter=/usr/bin/python3 -e ansible_ssh_private_key_file=/var/folders/gs/pzy2ccf56fz14fkhrcmjhph00000gp/T/ansible-key705286474 -i /var/folders/gs/pzy2ccf56fz14fkhrcmjhph00000gp/T/packer-provisioner-ansible4253597519 /Users/adamvu/stem-cd-integ-bot/packer-ansible/ansible/playbook.yml
amazon-ebs.ubuntu:
amazon-ebs.ubuntu: PLAY [Configure Ubuntu 22.04 AMI] **********************************************
amazon-ebs.ubuntu:
amazon-ebs.ubuntu: TASK [Gathering Facts] *********************************************************
amazon-ebs.ubuntu: ok: [default]
amazon-ebs.ubuntu:
amazon-ebs.ubuntu: TASK [system_updates : Update apt package index] *******************************
amazon-ebs.ubuntu: changed: [default]
amazon-ebs.ubuntu:
amazon-ebs.ubuntu: TASK [system_updates : Upgrade all packages] ***********************************
amazon-ebs.ubuntu: changed: [default]
amazon-ebs.ubuntu:
amazon-ebs.ubuntu: TASK [system_updates : Install security updates] *******************************
amazon-ebs.ubuntu: ok: [default]
amazon-ebs.ubuntu:
amazon-ebs.ubuntu: TASK [user_management : Create jenkins user] *************************************
amazon-ebs.ubuntu: changed: [default]
amazon-ebs.ubuntu:
amazon-ebs.ubuntu: TASK [user_management : Set up authorized key for jenkins user] ******************
amazon-ebs.ubuntu: changed: [default]
amazon-ebs.ubuntu:
amazon-ebs.ubuntu: TASK [user_management : Allow sudo without password] ***************************
amazon-ebs.ubuntu: changed: [default]
amazon-ebs.ubuntu:
amazon-ebs.ubuntu: TASK [package_installation : Install common system packages] *******************
amazon-ebs.ubuntu: changed: [default]
amazon-ebs.ubuntu:
amazon-ebs.ubuntu: PLAY RECAP *********************************************************************
amazon-ebs.ubuntu: default : ok=8 changed=6 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
amazon-ebs.ubuntu:
==> amazon-ebs.ubuntu: Stopping the source instance...
amazon-ebs.ubuntu: Stopping instance
==> amazon-ebs.ubuntu: Waiting for the instance to stop...
==> amazon-ebs.ubuntu: Creating AMI ubuntu-22-04-ansible-1744517631 from instance i-082d0913508638938
amazon-ebs.ubuntu: AMI: ami-01ba4796a04182845
==> amazon-ebs.ubuntu: Waiting for AMI to become ready...
==> amazon-ebs.ubuntu: Skipping Enable AMI deprecation...
==> amazon-ebs.ubuntu: Adding tags to AMI (ami-01ba4796a04182845)...
==> amazon-ebs.ubuntu: Tagging snapshot: snap-081aa13668138e0b1
==> amazon-ebs.ubuntu: Creating AMI tags
amazon-ebs.ubuntu: Adding tag: "Name": "Ubuntu 22.04 with Ansible"
==> amazon-ebs.ubuntu: Creating snapshot tags
==> amazon-ebs.ubuntu: Terminating the source AWS instance...
==> amazon-ebs.ubuntu: Cleaning up any extra volumes...
==> amazon-ebs.ubuntu: No volumes to clean up, skipping
==> amazon-ebs.ubuntu: Deleting temporary security group...
==> amazon-ebs.ubuntu: Deleting temporary keypair...
Build 'amazon-ebs.ubuntu' finished after 10 minutes 5 seconds.
==> Wait completed after 10 minutes 5 seconds
==> Builds finished. The artifacts of successful builds are:
--> amazon-ebs.ubuntu: AMIs were created:
us-east-1: ami-01ba4796a04182845
The build process:
- Launches a temporary EC2 instance
- Applies Ansible configurations
- Creates an AMI from the configured instance
- Cleans up temporary resources
Conclusion Link to heading
Building custom AMIs with Packer and Ansible provides a robust foundation for your infrastructure. This approach:
- Ensures consistency across environments
- Automates the build process
- Reduces manual configuration
- Improves security posture
- Speeds up deployment times