The pitch for portability is an option: build on abstractions, and if AWS raises prices or Google deprecates something you depend on or a customer demands Azure, you can move. Options have value. But options also have a premium, and the premium here is paid continuously, in engineering velocity, by every team on the platform, for as long as the abstraction exists — while the option is exercised, in the median company, never.

That is not an argument against ever doing it. It is an argument for pricing it honestly, which almost nobody does, because the cost shows up as a diffuse tax on feature delivery and the benefit shows up as a slide in a strategy deck.

What the abstraction actually costs

You get the intersection, not the union. A portable data layer means no DynamoDB streams, no BigQuery, no Aurora Serverless, no Spanner. You get PostgreSQL, which is excellent, and you get to operate it yourself or accept the managed variant on each cloud with its own quirks. The intersection of three clouds' managed services is roughly: object storage, VMs, a load balancer, Kubernetes, and a message queue with the weakest common semantics. Every capability above that line becomes something you build.

The build costs are concrete. A team that would have used SQS plus Lambda for an event pipeline instead runs Kafka on Kubernetes. That is a Strimzi operator, a storage class decision, partition rebalancing during node churn, a consumer group monitoring story, an upgrade path, and roughly a quarter of an engineer indefinitely. The managed version was $400 a month and zero engineers.

Abstractions leak at exactly the wrong moment. They hold during development and fail during incidents. Your storage abstraction wraps S3 and GCS behind one interface, which works until you need conditional writes, and S3's precondition semantics do not map onto GCS generation numbers cleanly. Your Kubernetes manifests are portable, until you need a load balancer with proxy protocol enabled and the annotation is provider-specific. IAM does not abstract at all — AWS IAM policies, GCP IAM bindings, and Azure RBAC have genuinely different models, and any layer claiming to unify them is either lowest-common-denominator or a leaky translation you will debug at 2 a.m.

Expertise fragments. Two clouds means every engineer is worse at both. Networking, IAM, quota behaviour, and failure modes are all deep-specialist knowledge, and splitting a team's attention across two providers does not produce two competencies, it produces one and a half. This is the cost nobody puts in the business case and it is frequently the largest one.

Commitment discounts halve. Savings Plans and Committed Use Discounts scale with the volume you can promise to one provider. Splitting workload 60/40 across two clouds means committing less to each, at worse rates. On a $5M annual spend, moving from a single-provider commitment at a 32 percent effective discount to a split at 22 percent is $500,000 a year — which is often larger than the negotiating leverage the split was supposed to buy.

Egress is a tollbooth. Any architecture with a chatty path across providers pays per gigabyte in one direction, forever. The EU Data Act and competitive pressure have pushed providers to waive egress fees for full customer exits, which is a genuine improvement, but it does not make steady-state cross-cloud traffic free. A service mesh spanning two clouds, or a database in one and its readers in another, generates continuous egress that dwarfs the compute savings that motivated the split.

Where the cost is lower than people think

Two things have genuinely improved and it would be dishonest not to say so.

Kubernetes really did commoditise the compute layer. A Deployment manifest is portable in a way that a CloudFormation template never was. The gap is now in the surrounding services rather than in the workload runtime, and that is a meaningful narrowing.

Terraform (and OpenTofu) did the same for provisioning mechanics. You still write provider-specific resources — aws_eks_cluster and google_container_cluster are not interchangeable — but the workflow, state management, review process, and module structure carry across. The skill is portable even when the code is not.

The right conclusion from these two facts is that infrastructure mechanics are portable and managed services are not. That suggests where the seam should be, if you draw one.

Three cases where it is the right call

1. Contractual requirement. Public sector, healthcare, financial services, and increasingly EU enterprises will put multi-cloud or sovereign-cloud clauses in the contract. If the revenue is contingent on it, the analysis is over — you are pricing the cost of the deal, not debating architecture. Do it deliberately: pick the specific workloads that must be portable and quarantine the portability requirement there, rather than letting it infect the whole platform.

2. A workload with genuinely different economics elsewhere. GPU capacity is the live example. If a specialised provider offers accelerator capacity at 40 percent below the hyperscaler rate with acceptable availability, and your training workload is batch, checkpointed, and touches only object storage, then running training on one provider and everything else on another is not a portability strategy — it is one workload placed where it is cheapest. The isolation is what makes it work: training jobs are architecturally separable from serving in a way that a transactional service is not.

3. Acquisition reality. You bought a company that runs on GCP. You are on AWS. Nobody is porting anything for eighteen months. This is multi-cloud by inheritance, and the correct response is a containment strategy — a clean interface between the two estates, no new dependencies crossing it, and a written plan with a date — rather than a portability abstraction imposed retroactively on both sides.

Notably absent: "leverage in negotiations." This is the most commonly cited reason and the weakest. Enterprise discounts respond to committed volume, growth rate, and how much the account matters strategically. A credible threat to move requires having actually moved something, and if you have moved something you already have the fragmentation costs. Most of the negotiating leverage people attribute to multi-cloud is available by simply running a competitive process at renewal.

The seam that is worth defending

If you decide portability matters, do not spread it evenly. Concentrate it at one layer and be explicit that everything else is single-cloud.

The layer that works: containerised workloads on Kubernetes, state in PostgreSQL and object storage, provisioned by Terraform modules with a provider-specific implementation behind a stable interface. Everything above is portable by construction. Everything below is written twice and owned by the platform team.

# modules/data-store/interface.tf — same variables on every cloud
variable "name"              { type = string }
variable "size_gb"           { type = number }
variable "ha"                { type = bool }
variable "backup_retention"  { type = number }
variable "pg_version"        { type = string }

output "endpoint"     { value = local.endpoint }
output "port"         { value = 5432 }
output "secret_ref"   { value = local.secret_ref }

With modules/data-store/aws/ using aws_rds_cluster and modules/data-store/gcp/ using google_sql_database_instance. Application teams consume the interface. The platform team eats the difference. This is honest — the complexity exists and someone pays for it — as opposed to a universal abstraction layer that pretends the difference is gone.

The critical discipline is that the interface must express only what both providers genuinely support. The moment you add a variable that is a no-op on one cloud, you have a portable interface with non-portable behaviour, which is worse than no abstraction because it is now invisible. That is the same class of mistake as the leaky platform abstraction, and it fails the same way.

The recommendation

Default to one cloud. Use its managed services aggressively, including the proprietary ones, because that is where the leverage is and paying an abstraction tax to avoid using what you are already paying for is a poor trade. Keep your data in formats you could export — Parquet in object storage, PostgreSQL rather than a proprietary engine, open table formats — because data gravity is what actually traps you, not compute.

Write down what a migration would cost, once, as an artifact. Twelve to twenty-four months and a substantial fraction of the platform team, for most companies. Then everyone can stop arguing about whether you are locked in — you are, the cost is known, and the discussion becomes whether that number is acceptable rather than whether it exists.

Revisit if one of the three cases above becomes true. Not before, and not because a vendor's architecture diagram made it look free. The same clarity applies to any platform investment: the question is not whether the capability is valuable in the abstract, but who pays the ongoing carrying cost and whether they agreed to. That framing — treating platform decisions as product decisions with real users and real opportunity cost — is what keeps this from becoming a matter of taste.