// case study · public project
Automating AWS AMI Creation for Linux and Windows Build Environments
Infrastructure automation work from two roles: building repeatable, validated AMI creation pipelines for Linux build servers and Windows servers using Packer, Ansible, and InSpec — with independently written public repositories demonstrating the approach.
Role & Context
Cleaver-Brooks
DevOps Engineer Lead
Aug 2022 – Sep 2023 · Milwaukee, USA
Dealer-FX
DevOps Engineer
Mar 2020 – Jun 2022 · Toronto, Canada
AMI automation was part of my infrastructure responsibilities at both Cleaver-Brooks and Dealer-FX. At both companies, manually built images caused consistency and auditability problems that I addressed by introducing automated, validated AMI pipelines. The toolchain was consistent — Packer, Ansible, and InSpec — and all three AMI types (Linux build server, Windows IIS web server, and Windows SQL Server) were in use at both companies, though the CI/CD tooling differed.
The public repositories linked at the bottom of this page are independent projects I built from scratch on my own machine to demonstrate this approach. They are not company exports.
Problem
Building AWS AMIs by hand — launching a base instance, configuring it manually, and snapshotting it — produces images that are difficult to reproduce, impossible to audit, and prone to configuration drift between environments. Each manual build is a one-off: there is no record of what was installed, in what order, or whether the final image met any compliance baseline.
At both companies, infrastructure teams relied on base AMIs without a systematic process for customising and validating them. The problem I was solving was making AMI creation repeatable, auditable, and validated. The same build that produces an image for a test account should produce an identical image for production, with automated checks confirming the result meets a defined baseline before the AMI is published.
My Contribution
I personally designed and implemented the AMI automation pipelines at both companies. The work I owned at each included:
Defining the Packer build templates — specifying the base AMI, instance configuration, provisioner sequence, and variable structure that kept environment-specific values out of source control.
Writing and organising the Ansible provisioners — structuring them so each installs exactly one component, making the provisioner list a readable manifest of what is in the image.
Building the InSpec compliance profiles — defining checks that run after provisioning and block AMI publication if the instance does not meet the baseline.
Wiring together the CI/CD pipeline — configuring branch-based account separation so feature branch builds target test and staging environments while only reviewed, merged builds reach production.
Documenting the AMI build process, the provisioner structure, and the compliance baseline for team use.
Approach & Toolchain
Packer
Build orchestration
Packer drives the entire AMI creation process. It launches a temporary EC2 instance from a base AMI, coordinates the provisioners, waits for them to complete, and creates the final AMI from the configured instance. The Packer template defines the builder, source AMI, instance type, VPC and subnet, and the sequence of provisioning steps. All environment-specific configuration is supplied through variables — no credentials or account details appear in the template.
Ansible
Package installation & configuration
Ansible playbooks handle software installation and configuration on the instance. Each playbook installs one component — a runtime, a build tool, a system library — and they run in sequence via Packer's provisioner list. This modular structure means playbooks can be added, removed, or reordered without touching the Packer template structure. For Linux, Ansible runs over SSH. For Windows, Packer bootstraps WinRM using a boot configuration script, then Ansible connects over WinRM.
InSpec
Compliance validation
After provisioning completes and the instance reboots, InSpec runs a compliance profile against the instance. The profile checks that each installed package or tool is present at the expected version — verifying by package manager query, command-line version output, or file existence depending on what each tool exposes. The build fails if InSpec checks do not pass, which means the AMI is only published once the instance has been confirmed to match the defined baseline.
CI/CD Pipeline
Automated delivery
The pipeline runs on every push. Branches other than main build and publish AMIs to test, development, and staging AWS accounts after InSpec validation passes. Merges to main target the production AWS account and, after InSpec validation, publish and share the final AMI to the configured production accounts. The AMI version number in the Packer template is incremented with each pull request to maintain a clear image version history. At Cleaver-Brooks, this pipeline was implemented with AWS CodePipeline, CodeBuild, CodeDeploy, and CodeCommit. At Dealer-FX, Azure DevOps served the same role.
Build Pipeline
Conceptual diagram — simplified for public presentation
What the Repositories Contain
Custom Linux build server AMI. Installs a broad set of language runtimes and development tools needed for CI/CD build agents.
Ansible provisioners
InSpec: Verifies each installed runtime and tool by version — package manager checks, command-line version output, and file existence (e.g. Azure DevOps agent run.sh). Build fails if any check does not pass.
Custom Windows web server AMI. Configures IIS with ASP.NET MVC, URL rewriting, and Web Deploy for application hosting.
Ansible provisioners
InSpec: Automated compliance testing confirms the IIS configuration, ASP.NET components, and tooling are correctly installed before the AMI is published. WinRM is bootstrapped via a boot_config script so Packer can communicate with the Windows instance.
Custom Windows SQL Server AMI. Provisions and configures SQL Server for use as a database instance in AWS environments.
Ansible provisioners
InSpec: InSpec validates the SQL Server installation and configuration meet the defined baseline before the AMI is published and shared to target accounts.
Engineering Decisions
One playbook per component
Each Ansible playbook installs exactly one thing. This makes the provisioner list in the Packer template a readable manifest of what is in the image, and means components can be added, removed, or updated independently. A single large playbook would be harder to debug when one step fails and harder to maintain when one component needs updating.
InSpec as a build gate, not a post-build audit
InSpec runs as part of the Packer build before the AMI is created. If the compliance checks fail, the build fails and no AMI is published. This means the published AMI is always a validated artifact — there is no risk of a non-compliant image reaching production because validation was skipped or treated as advisory.
Branch-based account separation
Feature branches build and publish AMIs to test, development, and staging AWS accounts after InSpec validation passes. Merges to main build and publish to the production account only. This keeps production AMI registries clean — only reviewed, PR-approved builds reach production — while still giving lower environments a validated AMI from every branch.
WinRM bootstrap for Windows provisioning
Packer cannot use SSH to provision Windows instances. A WinRM setup script is injected as user data on instance launch, bootstrapping the WinRM listener before Ansible attempts to connect. This allows the same Packer + Ansible pattern used for Linux to apply to Windows instances without requiring any manual instance configuration.
Environment variables for all secrets and configuration
No credentials, account IDs, VPC IDs, or subnet IDs appear in the committed Packer templates. All are passed as environment variables at build time — from the CI/CD pipeline's variable store, and locally from the engineer's shell. This keeps the repository clean of environment-specific configuration and avoids credential exposure in source control.
Outcomes
- →Replaced manually built, undocumented AMIs with a fully automated, version-controlled pipeline at both companies.
- →Every published AMI was validated against a defined compliance baseline before reaching any environment — InSpec failures blocked the build rather than producing a non-compliant image.
- →Infrastructure teams gained a clear, readable record of what each AMI contained through the structured Ansible provisioner list and Packer template.
- →Branch-based account separation gave teams validated AMIs in test and staging from every branch, while keeping the production AMI registry restricted to reviewed, merged builds.
- →The same Packer + Ansible + InSpec pattern scaled across all three AMI types — Linux build server, Windows IIS web server, and Windows SQL Server — at both companies.
Repository Links
Disclosure
This case study summarizes my engineering experience at Cleaver-Brooks and Dealer-FX. Technical descriptions and diagrams are simplified for public presentation. No proprietary company code, confidential data, or internal configuration is included.