Create an Amazon Machine Image (AMI) using EC2 Image Builder and AWS CLI
All the scripts in this article are
bash
scripts
Setup for macOS
Make sure you do this setup first:
Set variables
Set variables needed for next steps:
account_id=$(aws sts get-caller-identity --query Account --output text)
# change this to your desired region
region="us-west-2"
# EC2 Image Builder Instance Role
ib_instance_role="ec2-ib-instance-role"
# EC2 Image Builder Instance Profile
ib_instance_profile="ec2-ib-instance-profile"
# EC2 Image Builder Security Group
ib_security_group="ec2-ib-instance-sg"
Create instance profile
This is needed for the EC2 Image Builder to launch instances during image creation.
Create instance role
echo "Creating IAM role..."
aws iam create-role \
--role-name $ib_instance_role \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}'
echo "Attaching policies to the role..."
aws iam attach-role-policy \
--role-name $ib_instance_role \
--policy-arn arn:aws:iam::aws:policy/EC2InstanceProfileForImageBuilder
aws iam attach-role-policy \
--role-name $ib_instance_role \
--policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
Create instance profile
echo "Creating instance profile..."
aws iam create-instance-profile \
--instance-profile-name $ib_instance_profile
echo "Adding role to instance profile..."
aws iam add-role-to-instance-profile \
--instance-profile-name $ib_instance_profile \
--role-name $ib_instance_role
echo "Instance profile created: $ib_instance_profile"
Create security group
This is needed for the EC2 Image Builder to launch instances during image creation.
echo "Creating security group..."
sg_id=$(aws ec2 create-security-group \
--group-name $ib_security_group \
--description "Security group for Image Builder" \
--query 'GroupId' \
--output text)
echo "Security group created with ID: $sg_id"
echo "Adding inbound rule for SSH..."
aws ec2 authorize-security-group-ingress \
--group-id "$sg_id" \
--protocol tcp \
--port 22 \
--cidr "0.0.0.0/0"
echo "Adding inbound rule for HTTP..."
aws ec2 authorize-security-group-ingress \
--group-id "$sg_id" \
--protocol tcp \
--port 80 \
--cidr "0.0.0.0/0"
echo "Security group configured."
Create Recipe
Create temp working directory
Add a component for installing Nginx
Create nginx installation component YAML:
cat << EOF > nginx-component.yaml
name: InstallNginx
description: Installs and starts nginx
schemaVersion: 1.0
phases:
- name: build
steps:
- name: InstallNginx
action: ExecuteBash
inputs:
commands:
- sudo yum update -y
- sudo yum install nginx -y
- sudo systemctl start nginx
- sudo systemctl enable nginx
EOF
Create the component
component_arn=$(aws imagebuilder create-component \
--name "nginx-install-component" \
--semantic-version "1.0.0" \
--platform "Linux" \
--data "file://nginx-component.yaml" \
--region $region \
--query 'componentBuildVersionArn' \
--output text)
echo "Component ARN: $component_arn"
To list your own components:
To list the components available from Amazon:
list_amazon_components() {
local component_name="$1"
component_name=$(echo "$component_name" | tr '[:upper:]' '[:lower:]')
local next_token=""
echo "Listing Amazon components with name containing '$1' (case-insensitive) ..."
while true; do
if [ -z "$next_token" ]; then
result=$(aws imagebuilder list-components \
--owner Amazon \
--region $region \
--query 'componentVersionList[*].{Name:name,Version:version,ARN:arn}' \
--output text)
else
result=$(aws imagebuilder list-components \
--owner Amazon \
--region $region \
--next-token "$next_token" \
--query 'componentVersionList[*].{Name:name,Version:version,ARN:arn}' \
--output text)
fi
echo "$result" | while read -r name version arn; do
name_lower=$(echo "$name" | tr '[:upper:]' '[:lower:]')
if echo "$name_lower" | grep -q "$component_name"; then
printf "%-50s %-10s %s\n" "$name" "$version" "$arn"
fi
done
next_token=$(aws imagebuilder list-components \
--owner Amazon \
--region $region \
${next_token:+--next-token "$next_token"} \
--query 'nextToken' \
--output text)
[ "$next_token" = "None" ] && break
done
}
list_amazon_components "update-linux"
Create the recipe
List Amazon Linux images (via System Manager):
ami_id=$(aws ssm get-parameters --names /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64 --query 'Parameters[0].Value' --output text --region $region)
echo "Latest Amazon Linux 2023 AMI ID: $ami_id"
Create a YAML file for the image recipe :
cat << EOF > image-recipe.yaml
name: al2023-nginx-recipe
semanticVersion: 1.0.0
parentImage: $ami_id
components:
- componentArn: arn:aws:imagebuilder:us-west-2:aws:component/update-linux/1.0.2
- componentArn: $component_arn
EOF
Create Image Recipe:
recipe_arn=$(aws imagebuilder create-image-recipe \
--cli-input-yaml "file://image-recipe.yaml" \
--region $region \
--query 'imageRecipeArn' \
--output text)
echo "Recipe ARN: $recipe_arn"
Create infrastructure configuration
infra_config_arn=$(aws imagebuilder create-infrastructure-configuration \
--name "al2023-nginx-infra-config" \
--instance-types t2.micro \
--instance-profile-name $ib_instance_profile \
--region $region \
--query 'infrastructureConfigurationArn' \
--output text)
echo "Infrastructure Configuration ARN: $infra_config_arn"
Create distribution configuration
dist_config_arn=$(aws imagebuilder create-distribution-configuration \
--name "al2023-nginx-dist-config" \
--distributions "region=$region,amiDistributionConfiguration={name='al2023-nginx-{{imagebuilder:buildDate}}'}" \
--region $region \
--query 'distributionConfigurationArn' \
--output text)
echo "Distribution Configuration ARN: $dist_config_arn"
Create image pipeline
pipeline_arn=$(aws imagebuilder create-image-pipeline \
--name "al2023-nginx-pipeline" \
--image-recipe-arn "$recipe_arn" \
--infrastructure-configuration-arn "$infra_config_arn" \
--distribution-configuration-arn "$dist_config_arn" \
--region $region \
--query 'imagePipelineArn' \
--output text)
echo "Image Pipeline ARN: $pipeline_arn"
Start the image pipeline
execution_id=$(aws imagebuilder start-image-pipeline-execution \
--image-pipeline-arn "$pipeline_arn" \
--region $region \
--query 'imageBuildVersionArn' \
--output text)
echo "Image Pipeline Execution ID: $execution_id"
Monitor build progress
echo "Image Build Version ARN: $execution_id"
# Function to check build status
check_build_status() {
local status=$(aws imagebuilder get-image \
--image-build-version-arn "$execution_id" \
--region $region \
--query 'image.state.status' \
--output text)
echo "$status"
}
# Monitor build progress
echo "Monitoring build progress ..."
while true; do
status=$(check_build_status)
echo "Current status: $status"
if [[ "$status" == "AVAILABLE" ]]; then
echo "Build completed successfully!"
break
elif [[ "$status" == "FAILED" ]]; then
echo "Build failed. Check the AWS console for more details."
break
elif [[ "$status" == "CANCELLED" ]]; then
echo "Build was cancelled."
break
fi
sleep 10
done
# If build completed successfully, get the AMI ID
if [[ "$status" == "AVAILABLE" ]]; then
ami_id=$(aws imagebuilder get-image \
--image-build-version-arn "$execution_id" \
--region $region \
--query 'image.outputResources.amis[0].image' \
--output text)
echo "Created AMI ID: $ami_id"
fi
Cleanup
Set variables
image_pipeline_name="al2023-nginx-pipeline"
image_recipe_name="al2023-nginx-recipe"
component_name="nginx-install-component"
infrastructure_config_name="al2023-nginx-infra-config"
distribution_config_name="al2023-nginx-dist-config"
Delete Image Pipeline
echo "Deleting Image Pipeline: $image_pipeline_name"
pipeline_arn=$(aws imagebuilder list-image-pipelines --region $region --query "imagePipelineList[?name=='$image_pipeline_name'].arn | [0]" --output text)
aws imagebuilder delete-image-pipeline --image-pipeline-arn $pipeline_arn --region $region
echo "Waiting for Image Pipeline to be deleted..."
while aws imagebuilder list-image-pipelines --region $region --query 'imagePipelineList[].name' --output text | grep -q "$image_pipeline_name"; do
sleep 5
done
echo "Image Pipeline deleted successfully."
Delete Distribution Configuration
echo "Deleting Distribution Configuration: $distribution_config_name"
dist_config_arn=$(aws imagebuilder list-distribution-configurations --region $region --query "distributionConfigurationSummaryList[?name=='$distribution_config_name'].arn | [0]" --output text)
aws imagebuilder delete-distribution-configuration --distribution-configuration-arn $dist_config_arn --region $region
echo "Waiting for Distribution Configuration to be deleted..."
while aws imagebuilder list-distribution-configurations --region $region --query 'distributionConfigurationSummaryList[].name' --output text | grep -q "$distribution_config_name"; do
sleep 5
done
echo "Distribution Configuration deleted successfully."
Delete Infrastructure Configuration
echo "Deleting Infrastructure Configuration: $infrastructure_config_name"
infra_config_arn=$(aws imagebuilder list-infrastructure-configurations --region $region --query "infrastructureConfigurationSummaryList[?name=='$infrastructure_config_name'].arn | [0]" --output text)
aws imagebuilder delete-infrastructure-configuration --infrastructure-configuration-arn $infra_config_arn --region $region
echo "Waiting for Infrastructure Configuration to be deleted..."
while aws imagebuilder list-infrastructure-configurations --region $region --query 'infrastructureConfigurationSummaryList[].name' --output text | grep -q "$infrastructure_config_name"; do
sleep 5
done
echo "Infrastructure Configuration deleted successfully."
Delete Recipe
echo "Deleting Image Recipe: $image_recipe_name"
recipe_arn=$(aws imagebuilder list-image-recipes --region $region --query "imageRecipeSummaryList[?name=='$image_recipe_name'].arn | [0]" --output text)
aws imagebuilder delete-image-recipe --image-recipe-arn $recipe_arn --region $region
echo "Waiting for Image Recipe to be deleted..."
while aws imagebuilder list-image-recipes --region $region --query 'imageRecipeSummaryList[].name' --output text | grep -q "$image_recipe_name"; do
sleep 5
done
echo "Image Recipe deleted successfully."
Delete Component
echo "Deleting Component: $component_name"
component_arn=$(aws imagebuilder list-components --region $region --query "componentVersionList[?name=='$component_name'].arn | [0]" --output text)
aws imagebuilder delete-component --component-build-version-arn $component_arn/1 --region $region
echo "Waiting for Component to be deleted..."
while aws imagebuilder list-components --region $region --query 'componentVersionList[].name' --output text | grep -q "$component_name"; do
sleep 5
done
echo "Component deleted successfully."
Delete Instance Profile
echo "Deleting Instance Profile: $ib_instance_profile"
aws iam remove-role-from-instance-profile --instance-profile-name $ib_instance_profile --role-name $ib_instance_role
aws iam delete-instance-profile --instance-profile-name $ib_instance_profile
echo "Instance Profile deleted successfully."
Delete IAM Role
echo "Deleting IAM Role: $ib_instance_role"
aws iam detach-role-policy --role-name $ib_instance_role --policy-arn arn:aws:iam::aws:policy/EC2InstanceProfileForImageBuilder
aws iam detach-role-policy --role-name $ib_instance_role --policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
aws iam delete-role --role-name $ib_instance_role
echo "IAM Role deleted successfully."
Delete Security Group
echo "Deleting Security Group: $ib_security_group"
sg_id=$(aws ec2 describe-security-groups --filters "Name=group-name,Values=$ib_security_group" --query 'SecurityGroups[0].GroupId' --output text --region $region)
aws ec2 delete-security-group --group-id $sg_id --region $region
echo "Security Group deleted successfully."
Delete AMI
Optionally, delete the AMI if you want to remove it:
snapshot_id=$(aws ec2 describe-images --image-ids $ami_id --region $region --query 'Images[0].BlockDeviceMappings[0].Ebs.SnapshotId' --output text)
aws ec2 deregister-image --image-id $ami_id --region $region
echo "AMI deregistered: $ami_id"
aws ec2 delete-snapshot --snapshot-id $snapshot_id --region $region
echo "Snapshot deleted: $snapshot_id"