← All work

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.

  1. 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.
  2. 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.
  3. 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.
  4. AlarmsCloudWatch watches five signals across the load balancer, the instances, and the database, and publishes to an SNS topic when one trips.
  5. AzureA resource group with a Log Analytics workspace, a user-assigned managed identity, and a diagnostic setting, all created through the AzureRM provider.

Decisions

ADR 01

Build the new stack beside the old one

What
Terraform resources are prefixed atlas-tf-. The console-built originals kept atlas- 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.
ADR 02

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.
ADR 03

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.