Skill 76 · Amazon EC2 Image Builder
Subchapter 76.1
references/creating-images.mdMarkdown19 KBView on GitHub
Contents: Parameters · Base image · Build role · · · · · · ·
Ask for everything in one message so the build can run without stalling. (Commands in this skill use ${var} placeholders — substitute the actual values into the command text, including inside quoted JSON; ${account_id} is the account ID from aws sts get-caller-identity --query Account --output text.)
Default to Amazon Linux 2023 via the Amazon-managed Image Builder image, with the x.x.x wildcard so the recipe always tracks the latest version (this is also how scheduled rebuilds detect base-image updates — step 7b):
arn:aws:imagebuilder:${region}:aws:image/amazon-linux-2023-x86/x.x.x
arn:aws:imagebuilder:${region}:aws:image/amazon-linux-2023-arm64/x.x.xBrowse the Amazon-managed catalog with aws imagebuilder list-images --owner Amazon --region ${region}; if a create call rejects one of the AL2023 ARNs above, confirm the image name there. For an OS Image Builder doesn’t publish as an Amazon-managed image, fall back to a public SSM parameter (e.g. ssm:/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64). A specific AMI ID is a last resort for one-off builds only — it never picks up new releases or patches, so it’s wrong for recurring pipelines.
Valid --parent-image forms (the API’s term for the base image): an Image Builder image ARN, an ssm: parameter reference (name or ARN), an AMI ID, or a Marketplace product ID. An EC2-style image ARN (arn:aws:ec2:...:image/ami-...) is rejected.
Note: AL2023 ships with AWS CLI v2 preinstalled — don’t add a CLI install component to an AL2023 image.
The build role (the IAM role on the build instance’s instance profile) needs exactly two managed policies for AMI builds. Don’t confuse it with the build’s optional executionRole, the role Image Builder itself assumes to run the build’s workflows — see custom-workflows.md (opens in a new tab); the flows here don’t need one.
aws iam create-role --role-name ${name}-role --assume-role-policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"ec2.amazonaws.com"},"Action":"sts:AssumeRole","Condition":{"StringEquals":{"aws:SourceAccount":"${account_id}"}}}]}'
aws iam attach-role-policy --role-name ${name}-role --policy-arn arn:aws:iam::aws:policy/EC2InstanceProfileForImageBuilder
aws iam attach-role-policy --role-name ${name}-role --policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
aws iam create-instance-profile --instance-profile-name ${name}-role
aws iam add-role-to-instance-profile --instance-profile-name ${name}-role --role-name ${name}-role
# Only when using S3 build logging (step 6) - without this grant, S3 logs silently fail to deliver:
aws iam put-role-policy --role-name ${name}-role --policy-name s3-build-logs \
--policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"s3:PutObject","Resource":"arn:aws:s3:::${log_bucket}/imagebuilder/*"}]}'aws:SourceAccount condition scopes the role to instances launched in this account (confused-deputy protection) — EC2 denies credential vending when the condition doesn’t match.EC2InstanceProfileForImageBuilderECRContainerBuilds — that is for container builds only and grants unnecessary ECR access.{"Effect":"Allow","Action":"kms:GenerateDataKey","Resource":"${log_bucket_kms_key_arn}"} — without it PutObject is denied and log delivery fails silently.Check Amazon-managed components first: aws imagebuilder list-components --owner Amazon --region ${region}. The registry covers common needs — update-linux (OS patching), aws-cli-version-2-linux, amazon-cloudwatch-agent-linux, stig-build-linux hardening, language runtimes, and more (confirm current names in the list-components output). Reference them by ARN; the x.x.x wildcard version is valid in recipes and tracks the latest version. Check the AWSTOE UpdateOS action-module documentation for the OSes update-linux covers before adding it — it doesn’t cover AL2023, which patches via base-image refreshes instead (see step 9).
Write a custom component only for the user’s own software:
aws imagebuilder create-component --name ${name} --semantic-version 1.0.0 --platform Linux --data '<yaml>' --region ${region}--data (inline or file://). --uri requires a prior S3 upload. To validate a document without creating anything, add --dry-run: a valid document returns DryRunOperationException (“Request would have succeeded”), an invalid one returns the actual validation error. Minimal component document:name: ${component_name}
description: Installs ${software}
schemaVersion: 1.0
phases:
- name: build
steps:
- name: Install
action: ExecuteBash
inputs:
commands:
- <install commands>
- name: validate
steps:
- name: Verify
action: ExecuteBash
inputs:
commands:
- <a command that fails if the install is broken>get-component, and every command’s stdout/stderr streams to CloudWatch and the S3 log bucket. Have instances fetch secrets at run time from AWS Secrets Manager or SSM Parameter Store (SecureString).194: the instance reboots and the build re-runs that step from the top, so guard it with a marker file to avoid a reboot loop. A plain reboot command fails the step instead of resuming. The Amazon-managed update-linux component handles its own reboots.ResourceAlreadyExistsException — the document already exists, reuse the ARN from the error or from list-components. Re-creating with changed content silently creates a new build version (/1.0.0/2), and a recipe that references the version without a build suffix (.../my-component/1.0.0) resolves to the latest build version at each build. Bump the semantic version only when you want a new pinnable version. Never delete a component to “fix” this.componentBuildVersionArn from each response (create responses also include latestVersionReferences with ready-made wildcard ARNs).test phase: it runs in the TEST stage on a fresh instance launched from the new AMI, and the image only distributes if tests pass (Amazon-managed test components exist too, e.g. reboot-test-linux). Skip the test stage with --image-tests-configuration imageTestsEnabled=false on the build if speed matters more.aws imagebuilder create-image-recipe --name ${name}-recipe --semantic-version 1.0.0 \
--parent-image "${parent_image}" \
--components "componentArn=${component_arn_1}" "componentArn=${component_arn_2}" \
--block-device-mappings '[{"deviceName":"/dev/xvda","ebs":{"encrypted":true,"deleteOnTermination":true}}]' \
--region ${region}"kmsKeyId":"${kms_key_arn}" for a customer-managed key (required if the AMI will be shared cross-account — the default EBS key can’t be). Block device mappings are also where root volume size and type are set. Distribution-side kmsKeyId only encrypts copies made to other Regions or accounts.1.0.1) or a wildcard patch (--semantic-version 1.0.x), which the create call auto-assigns to the next free patch (wildcards auto-assign in create requests and resolve to the latest version in ARN references — see the Image Builder semantic versioning documentation). A pipeline that references the recipe with the x.x.x wildcard (arn:...:image-recipe/${name}-recipe/x.x.x) builds from the latest recipe version automatically; one pinned to a specific version needs repointing with update-image-pipeline.update-image-pipeline is a full replacement: re-pass every field. Omitting --status resets a DISABLED pipeline to ENABLED; omitting --schedule makes the pipeline manual.aws imagebuilder create-infrastructure-configuration --name ${name}-infra \
--instance-profile-name ${name}-role \
--instance-types ${type_a} ${type_b} \
--instance-metadata-options httpTokens=required \
--logging "s3Logs={s3BucketName=${log_bucket},s3KeyPrefix=imagebuilder}" \
--region ${region}Omit the --logging line unless the user wants the detailed on-instance AWSTOE logs delivered to an S3 bucket they own — these are more granular than the CloudWatch stream (which is on by default regardless) and useful for audits and deep debugging.
aws ec2 describe-instance-types --filters Name=current-generation,Values=true Name=processor-info.supported-architecture,Values=${architecture}. Image Builder picks by availability, which avoids capacity failures.httpTokens=required enforces IMDSv2 on build instances (AWS security best practice).aws:SecureTransport is false to enforce TLS in transit, and S3 server access logging where log access must be auditable.aws logs create-log-group --log-group-name /aws/imagebuilder/${name}-recipe then aws logs associate-kms-key --log-group-name /aws/imagebuilder/${name}-recipe --kms-key-id ${kms_key_arn}. Otherwise the group is created automatically with default at-rest encryption and the key can be associated after. The key’s policy must grant the logs.${region}.amazonaws.com service principal use of the key, scoped with the kms:EncryptionContext:aws:logs:arn condition to this account’s log groups — the CloudWatch Logs encryption documentation has the exact statement.--subnet-id and --security-group-ids for a subnet with a route to SSM — a private subnet with VPC endpoints for ssm, ssmmessages, ec2messages, and imagebuilder, plus S3 (add logs for CloudWatch logging) is the preferred production setup since the build instance then needs no internet path; a NAT gateway or a public IP also works. The security group needs no inbound rules — outbound HTTPS (443) is sufficient.--sns-topic-arn for build-completion notifications. Notification payloads include resource names, so use an encrypted topic with a restricted policy, and confirm the topic’s existing subscribers are authorized recipients before attaching it (aws sns list-subscriptions-by-topic --topic-arn ${topic_arn}). Check encryption with aws sns get-topic-attributes --topic-arn ${topic_arn} --query Attributes.KmsMasterKeyId; when creating a topic, always pass --attributes KmsMasterKeyId=${kms_key_id} — prefer a customer-managed key for production (key-policy control and auditable key use), with alias/aws/sns as the fallback minimum. With an SSE-KMS topic, grant Image Builder’s service-linked role access in the key policy — otherwise publishes fail silently.--terminate-instance-on-failure defaults to true (see troubleshooting.md (opens in a new tab) for when to set false).If distribution is needed (launch template updates, SSM parameter, other Regions), create the distribution configuration now per distribution-options.md (opens in a new tab) and capture ${dist_arn}.
No pipeline needed:
aws imagebuilder create-image --image-recipe-arn ${recipe_arn} \
--infrastructure-configuration-arn ${infra_arn} \
--region ${region}Add --distribution-configuration-arn ${dist_arn} if distribution was configured in step 6. This is aws imagebuilder create-image — not aws ec2 create-image, which snapshots a running instance. Capture imageBuildVersionArn from the response.
aws imagebuilder create-image-pipeline --name ${name} \
--image-recipe-arn ${recipe_arn} \
--infrastructure-configuration-arn ${infra_arn} \
--schedule 'scheduleExpression="cron(0 9 ? * mon *)",pipelineExecutionStartCondition=EXPRESSION_MATCH_AND_DEPENDENCY_UPDATES_AVAILABLE' \
--status ENABLED --region ${region}
aws imagebuilder start-image-pipeline-execution --image-pipeline-arn ${pipeline_arn} --region ${region}Add --distribution-configuration-arn ${dist_arn} if distribution was configured in step 6. Capture ${pipeline_arn} from the create response; start-image-pipeline-execution returns imageBuildVersionArn — capture it as the build ARN for step 8.
Schedules. Pipelines only run at their cron times — a dependency update never starts a build by itself. The start condition decides what happens at each tick:
EXPRESSION_MATCH_AND_DEPENDENCY_UPDATES_AVAILABLE (the default) runs the scheduled build only if a dependency updated since the last build, and skips it otherwise.
x.x.x wildcard reference (the default Amazon-managed parent image and Amazon-managed components), or a changed ssm: parent-image value — provided Image Builder can read the parameter at schedule time (public /aws/service/* parameters and parameters under /imagebuilder/ by default; other names need an execution role on the pipeline that can read the parameter).ssm: parameter the pipeline can’t read — such a pipeline never sees an update and never runs under this condition.EXPRESSION_MATCH_ONLY runs the build on every cron tick — use it for a guaranteed cadence regardless of updates.update-image-pipeline note in step 5 (full replacement).Vulnerability scanning (optional, also valid on create-image for one-off builds): add --image-scanning-configuration imageScanningEnabled=true. Two things to tell the user first: Amazon Inspector must already be activated for EC2 in the account (a paid, account-level decision — creation fails with a dependency error otherwise), and scan results are a findings snapshot in Inspector — scanning does not gate or fail the build (to get alerted on findings, use Inspector’s own EventBridge events; see the Inspector documentation).
aws imagebuilder get-image --image-build-version-arn ${build_arn} --region ${region}Confirm image.state.status is an in-flight state: PENDING, CREATING, BUILDING, TESTING, DISTRIBUTING, or INTEGRATING. Do not wait for completion (builds typically take 15–45 minutes); give the user this command to check later, and note the output AMI appears under image.outputResources.amis when AVAILABLE. If the status is FAILED, go to troubleshooting.md (opens in a new tab).
Automatic OS patching. Combine the 7b schedule with launch-template distribution (see distribution-options.md (opens in a new tab)) and patching runs itself: each new base-image release behind the wildcard parent is picked up at the next scheduled run, and Auto Scaling groups roll onto each new AMI. That base-image refresh is the patching mechanism for the default AL2023 base, which update-linux doesn’t cover (see step 4). For distros that update-linux covers, add it to the recipe as well, to pull the latest security updates between base-image releases. Note: every scheduled rebuild leaves the previous AMI and its snapshot behind — retiring old AMIs is out of scope here (see the Related skills table in SKILL.md).
Pipeline chaining (golden-base → app image). The direct form: the downstream recipe uses the upstream pipeline’s output image as its parent, with the x.x.x wildcard (arn:aws:imagebuilder:${region}:${account_id}:image/${upstream_image_name}/x.x.x); downstream pipelines with the dependency-updates start condition pick up each new upstream build at their next scheduled run. When consumers live outside Image Builder, publish to SSM instead — an ssm:/imagebuilder/${name}/latest parent also registers updates (see distribution-options.md (opens in a new tab)).