A reference implementation of a multi-cloud Infrastructure-as-Code platform, built to mirror how a platform engineering team actually ships consistent, repeatable infrastructure across AWS and Azure — Terraform for provisioning, Packer + Ansible for immutable golden images, and Bash/Python/PowerShell for the operational glue around both.
This isn't a single-cloud "hello world" — every component is deliberately built twice, once per cloud, with matching structure, so the repo demonstrates real multi-cloud proficiency rather than a Terraform demo with an Azure folder bolted on.
Most IaC portfolios stop at "here's a Terraform module that deploys a VM." This one is built around the full lifecycle a DevOps engineer actually owns:
- Bake a hardened, pre-configured server image (Packer + Ansible)
- Provision environment-specific infrastructure around it (Terraform)
- Operate it — deploy, validate, audit, clean up — with scripting (Python, Bash, PowerShell)
- Gate all of it through CI (GitHub Actions: fmt, validate, security scan)
The result is infrastructure where servers boot already-configured and hardened, instead of converging at boot time — and where the AWS and Azure implementations are structurally close enough that an engineer fluent in one can read the other in minutes.
| Layer | AWS | Azure |
|---|---|---|
| Image build | Packer amazon-ebs source |
Packer azure-arm source |
| Configuration | Ansible (site.yml, shared across both) |
Ansible (site.yml, shared across both) |
| Network | VPC, public/private subnets, NAT Gateway | VNet, subnet |
| Security perimeter | Security Group (SSH from trusted CIDR, HTTPS only) | NSG (default-deny inbound, explicit allow rules) |
| Compute | EC2 fleet launched from golden AMI | VM fleet launched from golden Managed Image |
| State backend | S3 + DynamoDB lock table | Azure Storage container |
| Hardening baked into image | IMDSv2 enforced, encrypted EBS, fail2ban, UFW default-deny, no root SSH, no password auth |
Same baseline, applied via the identical Ansible role |
Full design rationale: docs/ARCHITECTURE.md
Day-2 operations: docs/RUNBOOK.md
cloudforge-multicloud-iac/
├── terraform/
│ ├── aws/ # VPC, Security Group, EC2 — modular root config
│ │ ├── modules/{vpc,security-group,ec2}/
│ │ └── environments/{dev,prod}/terraform.tfvars
│ └── azure/ # VNet, NSG, VM — structurally mirrored
│ ├── modules/{vnet,nsg,vm}/
│ └── environments/{dev,prod}/terraform.tfvars
├── packer/
│ ├── aws/golden-image-aws.pkr.hcl
│ └── azure/golden-image-azure.pkr.hcl
├── ansible/
│ ├── playbooks/site.yml
│ └── roles/{common,security-hardening,monitoring-agent,docker}/
├── scripts/
│ ├── python/ # multi_cloud_inventory.py, cost_estimator.py
│ ├── bash/ # deploy.sh, tf_validate_all.sh, aws_resource_cleanup.sh
│ └── powershell/ # Deploy-AzureInfra.ps1, Azure-ResourceAudit.ps1, Invoke-GoldenImageBuild.ps1
├── .github/workflows/ # terraform-ci.yml, packer-build.yml
└── docs/ # ARCHITECTURE.md, RUNBOOK.md
Infrastructure as Code (Terraform) — Two independently-deployable root
modules with reusable child modules, input validation, terraform.tfvars
per environment, and remote state with locking. Not a flat main.tf — real
module boundaries (network / security / compute).
Golden image engineering (Packer + Ansible) — Both cloud builds call
the same Ansible playbook (ansible/playbooks/site.yml), so configuration
drift between AWS and Azure images is structurally impossible. Roles cover
baseline packages, security hardening, a lightweight health-check agent,
and Docker.
Scripting & automation (Python / Bash / PowerShell) — deploy.sh and
its PowerShell twin Deploy-AzureInfra.ps1 wrap Terraform with environment
validation and a destroy confirmation gate. multi_cloud_inventory.py
normalizes running compute across both clouds into one JSON shape.
Azure-ResourceAudit.ps1 flags tagging-policy drift. aws_resource_cleanup.sh
is a dry-run-by-default cost guardrail for stale dev resources.
CI/CD principles — .github/workflows/terraform-ci.yml runs fmt,
validate, and a tfsec security scan across both cloud modules on every
PR, plus an automatic plan against the dev environment. packer-build.yml
validates both golden-image templates on every change to packer/ or
ansible/.
Multi-cloud / BU-enablement mindset — The mirrored module structure (same variable names, same layering, side-by-side environment folders) is intentional: it's what makes a codebase usable for guiding other teams through Terraform adoption rather than something only its original author can navigate.
# 1. Build a golden image (example: AWS)
cd packer/aws
packer init . && packer validate .
packer build -var-file=../variables/common.pkrvars.hcl .
# 2. Drop the resulting AMI ID into terraform/aws/environments/dev/terraform.tfvars
# 3. Plan and deploy
./scripts/bash/deploy.sh aws dev plan
./scripts/bash/deploy.sh aws dev applyWindows operators can run the equivalent via
scripts/powershell/Deploy-AzureInfra.ps1 and Invoke-GoldenImageBuild.ps1.
Full step-by-step instructions, including the Azure path: docs/RUNBOOK.md
Note: This is a structural/portfolio reference — cloud credentials are intentionally not wired into the public CI workflows. Wire your own AWS/Azure credentials (ideally via OIDC) as repo secrets to run real
apply/buildoperations.
Terraform AWS (VPC · EC2 · S3 · IAM) Azure (VNet · VM · NSG) Packer
Ansible Python Bash PowerShell GitHub Actions tfsec
MIT — see LICENSE.