In this guide we will discuss how one can create a customized RHEL 8 / CentOS 8 AMI for AWS using Image Builder. For those new to Image Builder, it is a tool used to create customized system images of Red Hat Enterprise Linux, including system images prepared for deployment on cloud platforms.

The image Builder automatically handles server setup details for each image output hence it is faster than manual methods of image creation. A command line tool composer-cli, is available, as well as a graphical user interface in the Cockpit web console.

Image Builder Blocks

  • Blueprint – This define customized system images by listing packages and customizations that will be part of the system. Blueprints are presented to the user as plain text in the Tom’s Obvious, Minimal Language (TOML) format.
  • Compose – Composes are individual builds of a system image, based on a particular version of a particular blueprint.
  • Customization – These are are specifications for the system, which are not packages. This includes users, groups, and SSH keys.

Image Builder output formats

The Image Builder enables you to build images for multiple output formats. See table below.

DescriptionCLI namefile extension
QEMU QCOW2 Imageqcow2.qcow2
Ext4 File System Imageext4-filesystem.img
Raw Partitioned Disk Imagepartitioned-disk.img
Live Bootable ISOlive-iso.iso
TAR Archivetar.tar
Amazon Machine Image Diskami.ami
Azure Disk Imagevhd.vhd
VMware Virtual Machine Diskvmdk.vmdk

Step 1: Install Image Builder packages

Before you can use Image Builder, the following packages need to be installed.

sudo yum -y install vim lorax-composer composer-cli cockpit-composer bash-completion

Enable Image Builder to start after each reboot:

sudo systemctl enable --now lorax-composer.socket

For UI access via Cockpit, enable it:

sudo systemctl enable --now cockpit.socket
sudo firewall-cmd --add-service=cockpit && sudo firewall-cmd --add-service=cockpit --permanent

Load the shell configuration script so that the autocomplete feature for the composer-cli command starts working immediately without reboot:

source  /etc/bash_completion.d/composer-cli

Step 2: Create Blueprint for Image Builder

We’ll use command line interface for this operation. But the same can be done from Cockpit web console. To use the interface, run the composer-cli command with suitable options and arguments.

This is the workflow Image Builder:

  1. Export (save) the blueprint definition to a plain text file
  2. Edit this file in a text editor
  3. Import (push) the blueprint text file back into Image Builder
  4. Run a compose to build an image from the blueprint
  5. Export the image file to download it

Add your $USER to the weldr group.

sudo usermod -aG weldr $USER
newgrp weldr

Creating an Image Builder blueprint:

$ vim rhel8-base.toml

Mine has been modified to look like below:

name = "rhel-8-base"
description = "A RHEL 8 Base Image"
version = "0.0.1"
groups = []

name = "vim"
version = "*"

name = "openssh-server"
version = "*"

name = "rsync"
version = "*"

name = "tmux"
version = "*"

name = "git"
version = "*"

name = "tree"
version = "*"

name = "bash-completion"
version = "*"

name = "lvm2"
version = "*"

name = "wget"
version = "*"

name = "firewalld"
version = "*"

name = "python3"
version = "*"

name = "python3-pip"
version = "*"

name = "telnet"
version = "*"

append = "net.ifnames=0"

name = "rheladmin"
description = " RHEL Admin User"
password = "hashed-user-password"
key = "your-ssh-pub-key"
home = "/home/rheladmin/"
shell = "/usr/bin/bash"
groups = ["users", "wheel"]

Replace hashed-user-password with the actual password hash. To generate the hash, use a command such as this:

python3 -c 'import crypt,getpass;pw=getpass.getpass();print(crypt.crypt(pw) if (pw==getpass.getpass("Confirm: ")) else exit())'

Check documentation page for all entries and customizations.

Push the blueprint back into Image Builder:

$ composer-cli blueprints push rhel8-base.toml

List available Image builders:

$ composer-cli  blueprints list

Step 3: Create a system image with Image Builder

Pass the start option to build the image for your CentOS / RHEL machine.

$ composer-cli compose start BLUEPRINT-NAME IMAGE-TYPE

To see available image types, run:

$ composer-cli compose types

So I’ll now start a compose using the blueprint created and output type.

$ composer-cli compose start rhel-8-base ami
Compose 036fb329-0443-48ad-9444-a1c70caa4b36 added to the queue

To check the status of the compose:

$ composer-cli compose status
036fb329-0443-48ad-9444-a1c70caa4b36 RUNNING  Sat Apr  4 15:41:12 2020 rhel-8-base     0.0.1 ami            

$ composer-cli compose status
036fb329-0443-48ad-9444-a1c70caa4b36 FINISHED Sat Apr  4 15:46:52 2020 rhel-8-base     0.0.1 ami              4668260352  

Once the compose is finished, download the resulting image file:

$ composer-cli compose image UUID

-- Example ---
$ composer-cli compose image 036fb329-0443-48ad-9444-a1c70caa4b36
036fb329-0443-48ad-9444-a1c70caa4b36-disk.ami: 4452.00 MB    

Step 4: Upload AMI Image to AWS

Install Python 3 and the pip tool:

sudo yum -y install python3 python3-pip

Install the AWS command-line tools with pip:

sudo pip3 install awscli

Configure the AWS command-line client according to your AWS access details:

$ aws configure
AWS Access Key ID [None]:
AWS Secret Access Key [None]:
Default region name [None]:
Default output format [None]:

Configure the AWS command-line client to use your bucket:

$ BUCKET=ami-image-bucket
$ aws s3 mb s3://$BUCKET

Confirm bucket creation:

$ aws s3 ls 
2020-04-04 15:49:47 ami-image-bucket

Create a vmimport S3 Role in IAM and grant it permissions to access S3:

printf '{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "" }, "Action": "sts:AssumeRole", "Condition": { "StringEquals":{ "sts:Externalid": "vmimport" } } } ] }' > trust-policy.json

printf '{ "Version":"2012-10-17", "Statement":[ { "Effect":"Allow", "Action":[ "s3:GetBucketLocation", "s3:GetObject", "s3:ListBucket" ], "Resource":[ "arn:aws:s3:::%s", "arn:aws:s3:::%s/*" ] }, { "Effect":"Allow", "Action":[ "ec2:ModifySnapshotAttribute", "ec2:CopySnapshot", "ec2:RegisterImage", "ec2:Describe*" ], "Resource":"*" } ] }' $BUCKET $BUCKET > role-policy.json

aws iam create-role --role-name vmimport --assume-role-policy-document file://trust-policy.json

aws iam put-role-policy --role-name vmimport --policy-name vmimport --policy-document file://role-policy.json

Uploading an AMI image to AWS:

$ BUCKET=ami-image-bucket
$ AMI=036fb329-0443-48ad-9444-a1c70caa4b36-disk.ami
$ aws s3 cp $AMI s3://$BUCKET
upload: ./036fb329-0443-48ad-9444-a1c70caa4b36-disk.ami to s3://ami-image-bucket/036fb329-0443-48ad-9444-a1c70caa4b36-disk.ami

After the upload to S3 ends, import the image as a snapshot into EC2:

printf '{ "Description": "my-image", "Format": "raw", "UserBucket": { "S3Bucket": "%s", "S3Key": "%s" } }' $BUCKET $AMI > containers.json

aws ec2 import-snapshot --disk-container file://containers.json

Sample output:

    "ImportTaskId": "import-snap-0617ccf6944d82089",
    "SnapshotTaskDetail": {
        "DiskImageSize": 0.0,
        "Format": "RAW",
        "Progress": "3",
        "Status": "active",
        "StatusMessage": "pending",
        "UserBucket": {
            "S3Bucket": "ami-image-bucket",
            "S3Key": "036fb329-0443-48ad-9444-a1c70caa4b36-disk.ami"

Confirm import process:

$ aws ec2 describe-import-snapshot-tasks --filters Name=task-state,Values=active
    "ImportSnapshotTasks": [
            "ImportTaskId": "import-snap-0617ccf6944d82089",
            "SnapshotTaskDetail": {
                "DiskImageSize": 4668260352.0,
                "Format": "RAW",
                "Progress": "94",
                "SnapshotId": "snap-0fd61ffa2f2cd4ad0",
                "Status": "active",
                "StatusMessage": "Preparing snapshot",
                "UserBucket": {
                    "S3Bucket": "ami-image-bucket",
                    "S3Key": "036fb329-0443-48ad-9444-a1c70caa4b36-disk.ami"
            "Tags": []

Login to AWS and confirm existence of Snapshot.

Build Custome CentOS RHEL AWS AMI Using Image Bulder 01

Create an image from the uploaded snapshot right clicking Snapshot on EC2 and selecting Create Image:

Build Custome CentOS RHEL AWS AMI Using Image Bulder 02

Give Image a name and set virtualization type, disk size, description e.t.c.

Build Custome CentOS RHEL AWS AMI Using Image Bulder 03

After creation, the image will be available on the AMI section.

Build Custome CentOS RHEL AWS AMI Using Image Bulder 05

AWS Recommended courses:


Similar guides:

Automate RHEL and CentOS Installation on KVM with Kickstart

Build AWS EC2 Machine Images (AMI) With Packer and Ansible

Your support is our everlasting motivation,
that cup of coffee is what keeps us going!

As we continue to grow, we would wish to reach and impact more people who visit and take advantage of the guides we have on our blog. This is a big task for us and we are so far extremely grateful for the kind people who have shown amazing support for our work over the time we have been online.

Thank You for your support as we work to give you the best of guides and articles. Click below to buy us a coffee.


Please enter your comment!
Please enter your name here