Multi-Cloud Abstraction Without the Lowest Common Denominator

The instinct that says “stay portable, avoid lock-in” is right about the goal and usually wrong about the method. Abstract everything to the lowest common denominator and you pay for two clouds while getting the capability of neither.

Most multi-cloud abstraction is sold as insurance and delivered as tax. The pitch is seductive: build once against a neutral interface, deploy anywhere, walk away from any provider that raises prices or degrades service. What actually happens is that the abstraction layer flattens every cloud to the features they share, the managed services that justified moving to the cloud in the first place get ruled out because they are not portable, and the firm ends up self-hosting databases and queues on raw compute to preserve an exit it will probably never exercise. That is not optionality. That is a standing cost paid to avoid a hypothetical.

The useful question is not “how do we stay portable” but “which specific dependencies would actually hurt us to unwind, and are those the ones we are abstracting.” Almost always, they are not.

Free · 4 minutes

Do you know where AI is already being used in your business — and what it can see?

Fourteen questions on shadow AI, data exposure, oversight, and governance debt — the gap between how fast AI is arriving and how much control you have over it. Banded finding on screen, full sheet by email.

Where abstraction pays and where it is just a tax

Portability has a price, and that price is not uniform across the estate. It is worth paying where three conditions hold together, and it is a dead-weight cost where they do not.

The switching cost is concentrated and high. Some dependencies are cheap to unwind because they are commodity and standardised: object storage speaks broadly the same idioms everywhere, container runtimes are portable by construction, Postgres is Postgres whoever operates it. Others are expensive because the data has gravity, the integration is deep, or the service has no equivalent elsewhere. Abstraction earns its keep on the second category, not the first.

The dependency sits on the exit-critical path. If a provider withdrew the service tomorrow, or doubled the price, or suffered a prolonged regional outage, would the business stop? A message bus carrying every payment instruction is exit-critical. The managed service transcribing call-centre audio for analytics is not — you would miss it, but you would survive a migration measured in weeks. Spend the abstraction budget on the path that stops the business, and let the rest bind to whatever native service does the job best.

A genuine second implementation exists. Abstracting behind an interface only buys optionality if there is a real provider on the other side of it. A proprietary service with no equivalent — a specific managed ledger, a particular ML platform, a bespoke analytics engine — cannot be made portable by wrapping it in your own type. The interface would have exactly one implementation, which is not portability; it is ceremony. Bind to it natively, accept the lock-in openly, and record it as a known risk rather than pretending an abstraction has neutralised it.

Weak on any one of the three and the lowest-common-denominator tax outweighs the optionality. This is the same calculation that sits under the board-level view of the multi-cloud versus single-cloud trade-off, and it is where multi-cloud stops being insurance and becomes tax: not in the decision to run on two clouds, but in the decision to abstract things that never needed it.

Selective abstraction with a composition layer

The engineering move that keeps this honest is a composition layer that abstracts the interface the application depends on, while leaving a deliberate seam for per-cloud native extension. You define your own resource type — a portable contract expressed in your terms — and you map it, per provider, onto whatever native service best satisfies it. Crossplane does this well because the abstract type and the concrete implementation are separate objects, so the application never names a cloud and the platform team can add or swap an implementation without touching consumers.

# A composition mapping the firm's own PortableBucket contract onto AWS S3.
# The application requests a PortableBucket; it never names a cloud. A second
# composition (selected by label) maps the same contract onto GCS or Azure Blob.
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: portablebucket-aws
  labels:
    provider: aws            # platform selects the implementation by label
spec:
  compositeTypeRef:
    apiVersion: platform.example.org/v1alpha1
    kind: PortableBucket    # the firm's portable interface, not a cloud type
  mode: Pipeline
  pipeline:
  - step: patch-and-transform
    functionRef:
      name: function-patch-and-transform
    input:
      apiVersion: pt.fn.crossplane.io/v1beta1
      kind: Resources
      resources:
      - name: bucket
        base:
          apiVersion: s3.aws.m.upbound.io/v1beta1
          kind: Bucket
          spec:
            forProvider:
              region: eu-west-1
        patches:
        # only the exit-critical attributes cross the abstraction boundary
        - fromFieldPath: spec.retentionClass
          toFieldPath: spec.forProvider.tags[retention]

The discipline that matters is in the last two lines, not the plumbing above them. Only the attributes that are exit-critical — the ones a second implementation must also honour — cross the abstraction boundary. Everything cloud-specific stays below it, in the composition, where a native feature can be used freely because it is not part of the portable contract. Terraform modules achieve the same shape with a per-provider implementation behind a stable variable interface; the tool matters less than the rule.

Designing the interface so the seam holds

An abstraction leaks the moment a cloud-specific concept appears in the portable contract. If your PortableBucket exposes an S3 bucket policy document, you have not abstracted S3 — you have renamed it, and the second implementation is now impossible. The interface has to be expressed in the firm’s own vocabulary of intent: a retention class, a residency requirement, an access tier — not the provider’s field names. Keep the contract deliberately narrow. Every attribute you promise is an attribute every future implementation must satisfy, so promise only what the exit case genuinely needs and let the rest bind natively below the line.

This is also where the abstraction earns its regulatory keep. For firms in scope of DORA, ICT services supporting a critical or important function must have a documented, tested exit strategy — and a composition layer that already isolates the exit-critical dependencies gives you something concrete to test, rather than a strategy that exists only on paper. The same logic applies at smaller scale, where scaling without lock-in is a survival concern rather than a supervisory one.

The cost of portability, stated honestly

Every abstraction you build is a layer someone has to own, version, test and debug when it leaks — and they all leak eventually. That cost is worth carrying for the handful of dependencies that would genuinely stop the business or breach an exit obligation if a provider went away. It is not worth carrying for the analytics pipeline you would happily rebuild in a quarter. Portability is not a virtue to be maximised. It is a cost to be spent, precisely, on the exits you can actually imagine needing — and nowhere else.

Free interactive tool

Website compliance checklist

What your site has to do, based on what it actually does

Answer as much or as little as you like — the list builds as you go. Nothing is stored against your name and no email is required.

Can you trust the architecture you have?

Architecture diagrams rarely show the reality of how systems actually operate. An independent review establishes what is really there.