| name | hft-image-builder |
| description | Build and extend hardened, latency-tuned golden AMIs on AWS EC2 Image Builder, managed with Terraform. The build matrix (OS x CPU architecture) is declared as data in terraform/locals.tf and fans out into recipes and pipelines automatically. Use this skill when asked to add or remove an OS or CPU architecture, add or edit a build/test component, change kernel/network/security tuning or compiler flags, adjust infrastructure (instance types, IAM, VPC), change distribution (regions/accounts/AMI naming), modify the build schedule, or review/troubleshoot the AMI pipeline. Also triggers for "add a new OS", "add a new component", "why native compilation", "bump the engine version", or "harden the image". |
| user_invocable | true |
HFT Image Builder Skill
Authoritative rules for the golden-AMI pipeline in this repo. The design rationale lives in
EC2_Image_Builder_HFT_Engine_Guide.md; the implementation lives in terraform/. This skill is
the contract between the two: follow it so the matrix stays reproducible, auditable, and
latency-correct.
The concrete values below (OS versions, CPU families, instance types, component names) describe
the current state of the repo — always verify them against terraform/locals.tf and
terraform/components/ before acting, as the matrix is designed to grow. The rules and
invariants are what this skill owns; they hold regardless of which OSes or CPU families are in
the matrix.
Note: the rules below are the baseline this sample ships with. Add your own org-specific
rules in the Customized Part section at the bottom — that section is yours to own.
1. The mental model (do not break it)
- Recipe = Base Image + ordered Components. What to build.
- Infra Config = instance type + IAM + network. Where to build.
- Distribution = regions/accounts + AMI naming. Where to ship.
- Pipeline = Recipe + Infra Config + Distribution + schedule. When to run.
The whole design exists to maximize reuse. Before any change, ask: does this preserve reuse, or
am I forking something that should stay shared?
2. The build matrix is data, not code
The matrix is declared once in terraform/locals.tf as local.recipes (one row per OS x CPU
combination); recipes and pipelines fan out from it with for_each. Never hand-write an extra
recipe or pipeline resource. To change the matrix, edit local.recipes (and var.base_amis /
local.infra as needed) and let for_each do the rest.
Each recipe row carries: an OS key, a CPU key, a base-image key, and an instruction-set arch.
Read local.recipes for the current values (today: Ubuntu 24.04/26.04 x AMD/Intel/ARM = 6 rows).
Invariant: base images are keyed by OS + instruction set, not by CPU vendor. CPU families that
share an instruction set (e.g. AMD EPYC and Intel Xeon both on x86_64) share one base image. CPU
differentiation is applied by a component (optimize-<cpu>) at build time, not by a different
base image. If you ever find yourself adding a per-vendor base image for a shared instruction set,
stop — that is the bug this design prevents.
3. Components — the building blocks
Components live in terraform/components/*.yml and are registered in terraform/components.tf.
They fall into three classes:
Shared Build (every recipe): workload install + OS-level tuning — currently
hft-engine-core, kernel-tuning, network-stack, security-hardening.
CPU-specific Build (exactly one per recipe): optimize-<cpu>, one per CPU family in the matrix.
Test (every recipe, always last): currently hft-validation.
Each recipe = all shared Build components + exactly 1 CPU-specific Build + the Test gate.
Rules:
- Order is load-bearing. Workload install → kernel/network/security tuning → CPU optimization →
validation last. The test component is always the final component (test gate before publish).
- Fix shared logic once. A bug in a shared component is fixed in the one YAML; every image in
the matrix inherits it on the next build. Never copy-paste a shared component to patch one arch.
- CPU tuning belongs in
optimize-*, not elsewhere. Arch-specific compiler flags (-march,
SIMD extensions like AVX-512 / SVE / Neon) live only in the matching optimize-<cpu> component;
do not leak arch-specific flags into shared components.
- Bump
component_version in locals.tf when you change any component's behavior. Image
Builder cuts a new document version; recipes pick it up. Unversioned edits are not reproducible.
TODO markers are deliberate. Proprietary bits (the engine binary fetch, the Amazon Inspector
scan) are stubbed so a build succeeds without secrets. grep -rn TODO terraform/components/
before treating an image as production-ready. Do not silently remove a TODO — wire it up or
leave it.
4. Infrastructure — build on the metal you ship
local.infra holds one infra config per CPU family in the matrix. Optimized code is compiled on
the matching native hardware — never cross-compile one CPU family's tuned binaries on another
family's host. Read local.infra for the current instance types (e.g. amd → ca families,
intel → ci, arm → Graviton c*g).
All infra configs share one IAM role/instance profile (iam.tf) and the same network.
var.subnet_id is required — build instances must run in a dedicated private subnet, never the
default VPC. var.security_group_ids optionally overrides the managed locked-down builder SG
(no inbound, egress 80/443 only) from network.tf. Keep the instance family aligned to the CPU
key — an arm infra config must use a Graviton family.
5. Distribution — one config, no drift
There is 1 distribution config, shared by every pipeline (distribution.tf). It owns target
regions/accounts, AMI naming/tagging, and launch permissions. Changing it changes every image
consistently — that's the point. Do not create per-pipeline distribution configs to special-case
one image; if you think you need to, raise it as a design question first.
6. Standard tasks (the playbooks)
Add a new OS version:
- Add base AMI IDs to
var.base_amis — one per instruction set (e.g. <os>-x86_64, <os>-arm64).
- Add one row per CPU family to
local.recipes, mirroring the existing rows.
- Do not touch components, infra configs, or distribution.
terraform plan — expect exactly +N recipes and +N pipelines (N = number of CPU families),
nothing else.
Add a new CPU architecture:
- Add an entry to
local.infra with the correct native instance family.
- Add a CPU-specific
optimize-<cpu> component (YAML + components.tf registration).
- Add one recipe row per existing OS.
- Wire the recipe → component and pipeline → infra mapping the same way the existing CPUs are.
Edit a component / bump the engine:
- Edit the YAML under
terraform/components/.
- Bump
component_version in locals.tf.
terraform plan, confirm only the intended component version changes.
Change the schedule: edit var.build_schedule_cron (default Mon 05:00 UTC). One cron fans out
every pipeline in parallel.
7. Verification before you call it done
cd terraform && terraform fmt -check && terraform validate
terraform plan and read the diff against your stated intent (e.g. "+3 recipes, +3 pipelines, 0
changes to components"). A surprise in the plan means a mistake in the matrix.
terraform apply creates pipelines but triggers no build. A build runs only on the cron or via
aws imagebuilder start-image-pipeline-execution. Never claim an AMI was produced from an apply.
- The test gate (the validation component) is what blocks a bad AMI from publishing. Don't weaken or
reorder it to make a build pass.
8. Hard rules (never silently violate)
- ❌ Don't add per-CPU-vendor base images for a shared instruction set (e.g. splitting AMD from
Intel on x86_64) — base images are keyed by OS + instruction set only.
- ❌ Don't hand-author individual recipe/pipeline resources — extend
local.recipes.
- ❌ Don't edit a component without bumping
component_version.
- ❌ Don't move the validation/test component out of last position.
- ❌ Don't cross-compile — each CPU family builds on its own native instance family.
- ❌ Don't fork shared components per-arch; put arch differences in
optimize-*.
- ❌ Don't remove a
TODO stub without wiring up the real (engine fetch / Inspector scan) step.
Customized Part
This section is owned by you (the adopting team). Add organization-specific rules below. They
take precedence over the baseline above where they conflict — but call out the conflict
explicitly so the rationale isn't lost. Suggested categories to fill in:
- Approved base AMIs / OS versions — which OSes and AMI IDs are sanctioned.
- Allowed instance types & regions — cost/compliance guardrails for infra and distribution.
- Mandatory components — e.g. a required corporate agent, logging, or compliance scan.
- Hardening baseline — CIS level, required sysctl/GRUB settings, banned packages.
- Tagging & naming — required tags, AMI naming convention, account topology.
- Approval / change control — who signs off on matrix or distribution changes.
(empty — to be completed by the adopting team)