Case study · Cloud infrastructure · 2026
Rebuilding a multi-cloud deployment as code
Atlas Logistics started as my B.S. capstone: a solo lift-and-shift of a logistics application into AWS and Azure, built by hand in the console. I rebuilt it in Terraform, one reviewable phase at a time, while the original kept running.
- Context
- B.S. Cloud Computing capstone, then a rebuild as code
- Clouds
- AWS and Azure
- Tools
- Terraform with the AWS and AzureRM providers, Git
- Cost
- About $40 a month while it ran
- Code
- github.com/scott-and/atlas-multicloud-migration
The problem
The console build worked. It was stress-tested live, and a simulated failover caused no interruption. But it existed only as clicked settings and screenshots, so nobody, including me, could rebuild it exactly, review a change before it happened, or say with confidence what was running.
The goal of the rebuild was simple to state: every resource defined in code, every change reviewed as a diff, and the original left alone until the new version proved itself.
How it fits together
Five parts, in the order a request meets them. On a wide screen, point at a step or a part of the drawing to connect the two.
- Load balancerTraffic enters the VPC through an internet gateway and reaches an Application Load Balancer, which health-checks every instance and forwards only to healthy ones.
- ComputeAn Auto Scaling group launches EC2 instances from a launch template and adds capacity when average CPU climbs. Each instance carries a Systems Manager profile, so it can be administered without SSH.
- DatabaseRDS MySQL in its own DB subnet group: encrypted at rest, gp3 storage, seven days of automated backups. The password is a sensitive Terraform variable, so it never prints in plan output.
- AlarmsCloudWatch watches five signals across the load balancer, the instances, and the database, and publishes to an SNS topic when one trips.
- AzureA resource group with a Log Analytics workspace, a user-assigned managed identity, and a diagnostic setting, all created through the AzureRM provider.
Decisions
Build the new stack beside the old one
- What
- Terraform resources are prefixed
atlas-tf-. The console-built originals keptatlas-and stayed live until Phase 6 retired them. - Why
- There is always a known-good version running. Nothing is migrated in place, so a mistake in the new stack can’t take down the old one.
- Considered
- Importing the console resources into Terraform state and managing them where they stood.
- Accepted tradeoff
- Two stacks cost more than one until the originals are retired, and two naming schemes have to stay clearly separate.
Ship in five phases, each one a reviewable commit
- What
- Network first, then compute and load balancing, then the database, then monitoring, then Azure. Each phase has its own commit message and its own plan to read before applying.
- Why
- Each phase can be planned, applied, and checked on its own. When something goes wrong, the damage is limited to one phase, and the history explains how the system was assembled.
- Considered
- Writing the whole configuration and applying it at once.
- Accepted tradeoff
- Slower to finish, with more intermediate states to reason about between phases.
Alarm on what users feel, and on slow failures
- What
- Five alarms: healthy hosts behind the load balancer, response time, EC2 CPU credit balance, RDS CPU, and RDS free storage, all publishing to one SNS topic.
- Why
- Healthy hosts and response time are what a user experiences. The other three catch problems that build quietly for hours before anything breaks.
- Considered
- Alarming on instance CPU alone, which says little about whether the application is actually working.
- Accepted tradeoff
- More thresholds to tune, and an occasional alert that turns out to be harmless.
What broke
Azure, in Phase 5. The AWS phases authenticated through IAM, which was already set up. Azure needed its own credentials, so Terraform had to manage a second set alongside the AWS ones, with a separate login and separate permissions. It was the most annoying part of the project, and it’s a big part of why I’d keep everything on AWS next time.
Where it stands
All six phases are complete. Phase 6 retired the console-built originals, and I’ve since shut down the Terraform stack as well. Everything is still defined in the repository, so it can be rebuilt from code.
What I’d change
I’d keep it all on AWS. I added Azure so the project would be multi-cloud, not because the system needed it. That meant a second provider, a second way of authenticating, and a second set of services to learn, for a workload AWS already covered. Multi-cloud earns its cost when there’s a real reason for it, like a requirement to run in both or a service only one provider offers.