Build, Scan, Approve & Share AWS AMIs with Atmos + Packer + GitHub Actions
Configure and use Atmos with Packer to build, scan, approve, and share a hardened Amazon Linux 2023 AMI — automated end-to-end with a GitHub Actions pipeline and a tree of custom commands.
In detail, this gist builds a hardened Amazon Linux 2023 AMI with Packer, validates it on a live test instance, optionally scans it, gates promotion behind a manual approval, tags the approved image ScanStatus=approved, and shares it across AWS accounts — all orchestrated by Atmos and driven from a GitHub Actions pipeline.
Gist, not a maintained example. This is a reference recipe shared as-is to demonstrate the pattern. Unlike the CI-tested examples, gists are not continuously validated, so adapt it to your environment and your current version of Atmos before relying on it.
It combines several Atmos features into a single production-shaped workflow you can clone and adapt.
What it teaches
This gist combines, in one project:
- Packer components in Atmos —
components.packerconfig andatmos packer init/build/output. - Stacks for Packer — every build input (source AMI, networking, encryption, tags, provisioner list) is a stack var, not hardcoded HCL.
- Go templating in stacks — the source AMI name resolves from an environment variable at build time.
- Nested custom commands — an
atmos ami <subcommand>command tree (get-ami-id, tag, list-tags, get-tag, launch-instance, list/terminate-instances, share) that wraps small, reviewable scripts. - CI/CD with a governance gate — a GitHub Actions pipeline using OIDC auth, ephemeral runners, and a manual approval Environment.
- Tag-based launch governance — a reference IAM/SCP policy that restricts EC2 launches to AMIs tagged
ScanStatus=approved.
Architecture
┌──────────────────────────────────────────────────────────────┐│ GitHub Actions Pipeline (.github/workflows/ami.yml) │├───────────────── ─────────────────────────────────────────────┤│ build (Packer via Atmos) → launch test instance ││ → health check → [optional] scan ││ → ⏸ manual approval gate (GitHub Environment) ││ → tag ScanStatus=approved → share AMI → cleanup │└───────────────┬──────────────────────────────────────────────┘│ atmos packer build / atmos ami …┌───────────────▼──────────────────────────────────────────────┐│ Atmos ││ • Packer component (components/packer/al2023/main.pkr.hcl) ││ • Stack (stacks/al2023.yaml) — all build inputs as vars ││ • Custom commands (atmos ami …) → scripts/atmos/*.sh │└───────────────┬──────────────────────────────────────────────┘│ packer build┌───────────────▼──────────────────────────────────────────────┐│ Packer (amazon-ebs) ││ provisioners: patch-os → harden → [optional] scan agent ││ → install-packages → finalize ││ post-processor: manifest.json │└──────────────────────────────────────────────────────────────┘
Repository layout
aws-ami-packer-github-actions/├── atmos.yaml # Packer config + `atmos ami` custom command tree├── components/packer/al2023/│ ├── main.pkr.hcl # Parameterized amazon-ebs Packer template│ └── scripts/ # Provisioners run *inside* the image (in order)│ ├── patch-os.sh│ ├── harden.sh│ ├── install-scan-agent.sh # OPTIONAL, off by default│ ├── install-packages.sh # Edit this list for your image│ └── finalize.sh├── stacks/al2023.yaml # All build inputs as vars (placeholders marked)├── scripts/atmos/ # Host-side helpers backing `atmos ami` commands│ ├── _lib.sh # Shared: resolve_ami_id, require_cmd/env│ └── *.sh├── .github/│ ├── actions/setup-tools/ # Composite action: install Atmos + Packer│ ├── actions/setup-aws-credentials/ # Composite action: OIDC role assumption│ └── workflows/│ └── ami.yml # Main governed pipeline└── docs/ # Reference IAM/SCP policies + customization checklist
Prerequisites
| Tool | Version (pinned in CI) | Purpose |
|---|---|---|
| Atmos | 1.220.0 | Orchestration |
| Packer | 1.15.3 | Image build |
| AWS CLI | v2 | atmos ami helpers |
You also need a standard AWS account with permission to build AMIs and launch
EC2 instances. A reference IAM policy is in docs/packer-build-iam-policy.json.
The default path runs with only a standard AWS account. The optional scan step (
install-scan-agent.sh) needs a private package repo and a scanner subscription — it is disabled by default.
Run it locally
# 1. Copy the gist into a new repo of your owncp -r gists/aws-ami-packer-github-actions/ my-ami-pipeline/cd my-ami-pipeline/# 2. Edit stacks/al2023.yaml — set region, networking, KMS, sharing targets, tags# (see docs/customization-checklist.md)# 3. Initialize the Packer plugins and buildatmos packer init al2023 -s al2023atmos packer build al2023 -s al2023# 4. Inspect / operate the result with the custom commandsatmos ami get-ami-id al2023 -s al2023atmos ami list-tags al2023 -s al2023atmos ami launch-instance al2023 -s al2023 --type t3.smallatmos ami list-instances al2023 -s al2023# 5. Promote and share (normally done by the pipeline after approval)atmos ami tag al2023 -s al2023 --key ScanStatus --value approvedatmos ami share al2023 -s al2023 --accounts 123456789012,123456789013
Run the governed pipeline (GitHub Actions)
- Set repository variables
AWS_OIDC_ROLE_ARNandAWS_REGION. - Create the OIDC build role using
docs/oidc-trust-policy.jsonanddocs/packer-build-iam-policy.json. - Create a GitHub Environment named
ami-approvaland add required reviewers. - Run the AMI Pipeline workflow (
workflow_dispatch). It builds, health-checks, waits for approval, then tags and shares the AMI.
The reference policies under docs/ are paste-ready (no comment keys, which AWS
rejects). See docs/README.md for per-policy notes and the placeholders
to replace, and docs/customization-checklist.md for
the full setup list.
Customize
- Build steps — reorder/trim
provisioner_shell_scriptsand editinstall-packages.sh. - Hardening — toggle
ENABLE_FIREWALL/ENABLE_SELINUX_ENFORCINGviaprovisioner_env_vars. - Scanning — set
ENABLE_SCAN_AGENT=true+SCAN_AGENT_REPO_URLand fill ininstall-scan-agent.sh. - Governance — attach
docs/launch-restriction-scp.jsonto enforce "launch only approved AMIs". The SCP exempts the Packer build/test role (aws:PrincipalArn) so the pipeline's health-check step can still launch the freshly built, not-yet-approved AMI — replaceYOUR_PACKER_BUILD_ROLEbefore attaching, or a blanket deny will break the build.
Clean up
# Terminate any test instances launched from the AMIatmos ami terminate-instances al2023 -s al2023# Capture the AMI ID and region you want to removeami_id="$(atmos ami get-ami-id al2023 -s al2023)"region="us-east-2"# Find the EBS snapshot(s) backing the AMI *before* deregistering itsnapshot_ids="$(aws ec2 describe-images --region "$region" --image-ids "$ami_id" \--query 'Images[0].BlockDeviceMappings[].Ebs.SnapshotId' --output text)"# Deregister the AMIaws ec2 deregister-image --region "$region" --image-id "$ami_id"# Delete the backing snapshot(s) — deregistering the AMI does NOT remove them,# and leftover snapshots keep incurring EBS storage charges.for snap in $snapshot_ids; doaws ec2 delete-snapshot --region "$region" --snapshot-id "$snap"done