Dec 17, 2025 - 13 min read
Building in the Cloud: AWS, GCP, Azure, and the Tradeoffs Between Them
How to select a cloud ecosystem and a deployment model using workload and organizational constraints.
I've migrated a system off a cloud exactly once, and it took longer than building it the first time. That experience shapes almost everything in this piece: the provider decision is not really a technology decision, it's an organizational commitment with a technology attached to it. You're not picking a set of APIs. You're picking who you'll be paging at 3am, whose IAM model your security team will have to reason about for the next five years, and whose pricing page will show up in a budget review long after everyone's forgotten why the original choice was made.
So the framing I'd push back on immediately is "which cloud is best." None of them are best. Each is a different bet on what your organization will need to be good at.
Compare the platforms as systems, not service lists
AWS has the broadest catalog and the deepest ecosystem of partners, hires, and prior art — if you're doing something unusual, someone has already done it on AWS and written about it. GCP is unusually coherent where it's strong: BigQuery, Kubernetes (which originated there), and the data/analytics stack feel like they were designed by the same team with the same opinions, because they mostly were. Azure's real advantage shows up the moment your organization already runs on Microsoft identity and enterprise tooling — Entra ID integration, hybrid AD scenarios, and enterprise agreements that procurement already understands.
None of that is the actual decision criterion. The actual criterion is: which platform can your organization secure and operate repeatedly, without the outcome depending on which two engineers happen to be on-call. A brilliant architecture that only one person on your team can debug at 2am is not a brilliant architecture — it's a liability with good documentation.
Evaluate along these axes, in this order, because later axes are cheap to fix and earlier ones aren't:
- Identity and access model. Can your security team express least-privilege access the way they actually think about it — by team, by environment, by data sensitivity — or will every permission model become a bespoke translation exercise? This is the hardest thing to retrofit, because by the time you notice it's wrong, hundreds of roles and policies already assume the shape you started with.
- Landing zone maturity. Does the platform give you a credible starting point for multi-account/multi-project structure, guardrails, and policy-as-code, or are you building that scaffolding from scratch? A landing zone isn't infrastructure, it's the constitution the rest of your infrastructure operates under.
- Network controls and regional footprint. Does your latency and data-residency map actually align with where this provider has mature regions — not just "a region exists there," but full-service parity in that region.
- Skills you already have, and skills you can hire for. The best-designed platform is worth less than a platform your team can operate confidently, and hiring into a rare skill set is a recurring tax, not a one-time cost.
- Support model and enterprise agreement fit. At scale, your support tier and contractual relationship with the provider is itself an operational dependency — treat it as part of the architecture, not an afterthought for procurement.
- Total operational cost, which is not the same as the pricing calculator's output. It's egress, it's the hidden cost of services that only make sense at a scale you haven't reached yet, and it's the engineering time spent working around platform quirks.
Use managed services aggressively where they remove undifferentiated heavy lifting — running a database, patching a message broker, operating a Kubernetes control plane — but be honest about which managed services are actually removing operational burden versus which are just relocating it into a proprietary API surface you can't easily leave. "Managed" and "portable" are different axes; don't let vendor marketing collapse them into one.
Multi-cloud is a business continuity or regulatory decision, not a virtue. I want to be blunt about this because it's the most common architecture-review conversation I have: running the same workload actively on two providers roughly doubles your operational surface area — two IAM models to secure, two networking models to reason about, two sets of managed-service quirks to learn — in exchange for a resilience property most organizations never actually need and rarely test. If your regulator requires it, or a single-provider outage is genuinely existential to your business, that's a real reason. "We don't want to be locked in" is usually not a real reason; it's a feeling, and the feeling is often more expensive than the lock-in it's avoiding.
The landing zone as the actual foundation
The part of this that's easy to skip past is that identity comes before everything, including the landing zone itself. The landing zone is downstream of your identity provider's structure — how you model teams, environments, and data sensitivity in your IdP determines what the landing zone's account/project boundaries can even express. Get the identity model wrong and the landing zone inherits that mistake permanently, because migrating account structure later means moving live workloads across trust boundaries, which is exactly the kind of expensive-to-reverse decision this whole discipline exists to avoid.
Central observability deserves to sit next to messaging and compute in this diagram, not off to the side as a "nice to have" — because a landing zone without centralized, cross-account log and trace aggregation from day one means every incident response starts with someone hunting for where the logs actually live. Retrofitting observability across dozens of existing accounts is a project in its own right; building it into the landing zone template from account #1 is nearly free.
CI/CD sitting at the bottom of this chain is deliberate: your deployment pipeline should be constrained by the landing zone's policy and network model, not treated as a separate system that happens to have its own credentials into your cloud. A pipeline with broader IAM permissions than the humans it deploys on behalf of is a standing incident waiting for a compromised dependency.
Pick the compute shape last, not first
This is the section people want to start with, and it's the section that should come after everything above, because the "right" compute shape only makes sense once you know what you're securing and operating it inside of.
- Serverless functions fit bursty, event-driven work with unpredictable or spiky load — the classic fit is "something happens sometimes, and when it does, do a bounded unit of work." The tradeoff is cold starts, execution time limits, and a programming model that punishes long-running or stateful logic. Don't force a genuinely continuous service into a function just because "serverless" sounds like the modern default.
- Containers fit portable, continuously running services where you want a consistent runtime across dev, staging, and prod without owning the orchestration layer yourself. This is the right default for most application services.
- Kubernetes earns its very real operational cost when a platform team needs standardized orchestration across many teams and services at meaningful scale — custom scheduling, multi-tenant isolation, a genuine need for the portability and ecosystem it brings. It is not the default; it's a deliberate investment in a platform team's time, and that team's ongoing existence is part of the cost, not a one-time setup fee.
- A managed application platform — Cloud Run, App Runner, Azure Container Apps, or equivalent — is enough for a large fraction of workloads that people reach for Kubernetes to solve. If you don't currently have more than a handful of services and no dedicated platform team, this is very likely where you should stop, not where you should feel like you're settling.
Portability comes from clean domain boundaries, exportable data formats, and automated infrastructure-as-code — not from artificially restricting every workload to whatever the lowest-common-denominator service across three clouds happens to be. I've watched teams pay a permanent complexity tax by avoiding a provider's best-fit managed service — a superior queue, a superior data warehouse — in the name of hypothetical future portability they never exercised. If you actually need to move providers later, the thing that saves you is a well-modeled domain and infrastructure you can redeploy, not the fact that you never used anything provider-specific in the first place.
Design the cost and exit paths as part of the architecture, not after it
Cost modeling deserves the same rigor as capacity planning, because it is capacity planning, just denominated in currency instead of requests per second. Model steady-state and peak compute separately — peak is usually where the surprising bill comes from. Model storage growth as a curve, not a snapshot, especially for anything with retention requirements. Egress is the line item that ambushes the most teams, because it scales with success in a way that's easy to miss in a proof of concept that never left one region. Add observability, support tier, and engineer time actually spent operating the thing — a "cheap" managed service that eats a quarter of an engineer's time in workarounds is not cheap.
Make cost visible per product, not just per account, by tagging resources consistently from the start — this is one of those disciplines that's nearly free in month one and expensive to retrofit in month twenty-four, once untagged spend has accumulated across hundreds of resources nobody remembers provisioning.
And for any managed service you'd genuinely struggle to replace — your primary database, your core message broker, your identity provider — document three things before you adopt it, not after an incident forces you to: how you'd restore from backup, how you'd export your data in a usable format, and what a credible replacement path looks like if the provider changes terms, pricing, or availability under you. You're not doing this because you expect to need it. You're doing it because the fifteen minutes it takes to write down "we don't actually know" is the cheapest possible way to discover a dependency you can't unwind before it's load-bearing.
The actual test
The right cloud decision, two years out, isn't the one that looked most impressive in the vendor comparison. It's the one where your team can operate the platform without heroics, your identity model still makes sense as the org has grown, and the one time you needed the exit path you'd documented, it actually worked. Everything else is a keynote slide.