Back to Blog
Multi-CloudCloud StrategyVendor Lock-InEU Data ActDORAInfrastructure

Multi-Cloud Strategy: How to Avoid Vendor Lock-In Without Overengineering

Vendor lock-in is not one thing. Inventory it per layer, price your exit, test it by rebuilding staging from code, and see what DORA and the EU Data Act change.

P
Davi Nunes
March 8, 2026Updated October 30, 202611 min read

Most conversations about vendor lock-in start with the wrong question: whether to run on two clouds. The useful one is narrower and harder: what it would cost to leave your current provider within twelve months, how long it would take, and what would break first.

Few teams can answer that, because lock-in is not one thing. It is a stack of separate dependencies, each with its own exit cost. A second cloud addresses some of them and doubles your operating surface in exchange. Knowing your exit cost, and testing it, addresses all of them for far less effort.

Lock-In Is a Stack, Not a Single Risk

Score each layer by its cost to leave: engineering time to rebuild it elsewhere, plus one-off money such as data transfer, plus cutover risk. The ratings below are typical starting points, not verdicts.

  • Compute: usually low. Containerized workloads on Kubernetes move with little change. Machine images, instance metadata calls in boot scripts and load balancer annotations do not.
  • Managed databases: low to high. Managed PostgreSQL or MySQL is a standard engine; the data leaves with a dump or logical replication. A proprietary database with its own data model and query API means rewriting every query.
  • Queues and event buses: medium. Code that speaks an open protocol (AMQP, MQTT, the Kafka protocol) moves cheaply. A proprietary queue API needs an adapter rewrite, and the semantics (delivery guarantees, ordering, retries) differ more than the APIs.
  • Identity and IAM: high, and the most underestimated. Every role, policy and service-account binding is written in one provider's policy language and does not translate. If staff sign in through the provider's directory rather than your own identity provider, every human account moves too.
  • Networking: medium. Subnets and firewall rules get rebuilt. The painful part sits outside your estate: partners who allowlisted your egress IP addresses.
  • Observability: low to medium. With provider-native agents, every dashboard and alert is rebuilt by hand. With OpenTelemetry and Prometheus-compatible metrics, you change an exporter endpoint.
  • Proprietary serverless: high. The function code often ports. The triggers, event wiring and workflow definitions around it do not, and that glue is where business logic accumulates.
  • Data and egress: grows every month. Every terabyte you add raises the transfer cost and lengthens the cutover window, even if the architecture never changes.

Build the inventory for your own estate

Three sources give you a complete list in a day. First, the bill: export a year of billing data grouped by service. Every service on it is a dependency, including forgotten ones.

Second, your Terraform state. Count resource types per state:

bash
terraform state list \
  | sed -E 's/\[[^]]*\]//g' \
  | awk -F. '{print $(NF-1)}' \
  | sort | uniq -c | sort -rn

Each type's prefix names the provider and the rest names the service. Anything on the bill but missing here was created by hand, which is a finding in itself.

Third, the code: search your repositories for provider SDK imports. Each one is business logic talking to a provider instead of to an interface you own.

Record each dependency in a file kept in Git next to the code:

yaml
- layer: managed-database
  service: managed PostgreSQL 16
  used_by: [orders-api, billing-worker]
  portable_target: any PostgreSQL 16
  blockers:
    - two provider-only extensions
    - database login through provider IAM
  data_volume_tb: 1.4
  cost_to_leave: "3 engineer-weeks, one-off egress, one maintenance window"
  owner: platform-team
  last_reviewed: 2026-10-01

Put the cost to leave in engineer-weeks and money, not low, medium or high. A number can be challenged and corrected after a test.

The Exit Plan Is a Document You Maintain

An exit strategy that lives in someone's head is a hope. Written down, it is a few pages per critical system:

  • Triggers. What would start an exit (a price change beyond your threshold, a contract dispute, a regulator's instruction) and who decides.
  • A target per layer. Where each inventory entry would go, named precisely: "self-managed PostgreSQL on Kubernetes", not "somewhere else".
  • The data plan. Volumes, formats, transfer method and time, egress cost, and how changes sync while both sides run.
  • Sequence and rollback. The order systems move in, the parallel-run period, and the point of no return.
  • Contract terms. Notice periods and data retrieval terms, taken from the signed contract.
  • External dependencies. IP allowlists at partners, hard-coded endpoints at customers, certificates, DNS delegations.
  • The last test. Date, result and open findings of the latest portability test.

Give it one named owner, usually the infrastructure or platform lead, with sign-off from whoever owns supplier risk. Refresh the inventory quarterly, review the plan when the architecture changes, and test it at least once a year. An untested plan is a draft.

A Portability Test You Can Run in a Sprint

Rebuild your staging environment from code, in a different account or region, and time it. A different provider is the full version, but a fresh account already exposes most problems. Four rules keep the result honest:

  1. 1Start from an empty account with nothing created by hand.
  2. 2Only what is in Git counts. Every console click or copied value is logged as a finding.
  3. 3Restore data from backups, not from the live database, so you test the backups too.
  4. 4Stop the clock when smoke tests pass, not when the last apply finishes.

Point the same code at a new state backend and variable file:

bash
terraform init -reconfigure -backend-config=backends/portability-test.hcl
time terraform apply -var-file=env/portability-test.tfvars

Then deploy through your normal pipeline and restore the data. The failures repeat from team to team:

  • Hard-coded identifiers. Account IDs, region names and bucket names baked into code, Helm values and pipeline variables. Run grep -rnF -e "$ACCOUNT_ID" -e "$REGION" --exclude-dir=.git . in each repository first.
  • Resources never in code. A DNS record or role fixed in the console during an incident. The apply succeeds; the application fails.
  • The secrets bootstrap. The new secret store is empty and nobody holds the complete list of what goes in it.
  • Encryption keys. Backups encrypted with a key only the original account can use will not restore anywhere else.
  • Quotas and ordering. A fresh account starts on default quotas, and dependencies masked by years of incremental applies surface on the first apply from zero.
  • Images. Pulled from a registry the new account cannot reach, or referenced by mutable tags.

Record two numbers: hours from empty account to passing smoke tests, and manual steps. Fix the top findings and run it again until both fall.

Want a second pair of eyes before you run it? Our free assessment builds the per-layer inventory from your billing export and Terraform state, puts a cost to leave on each entry, and flags what would break in a from-scratch rebuild. Request a free lock-in assessment.

Data Gravity and Egress: Do the Arithmetic

This is data gravity: the jobs, services and backups that read your data end up wherever the largest datasets live, so data volume sets the pace of any exit. The arithmetic fits in three lines:

text
one-off transfer cost = data_GB x egress_price_per_GB
transfer time (days)  = data_TB x 8000 / (link_Gbps x utilization x 86400)
monthly cross-cloud   = daily_cross_cloud_GB x 30 x egress_price_per_GB

A worked example; replace the numbers with yours. Say you hold 40 TB in object storage and 2 TB in databases, and take €0.08 per GB as an example egress price. It is not a quote: real price lists are tiered and change.

  • One-off transfer: 42,000 GB x €0.08 = €3,360. Small next to the engineer-weeks in your inventory.
  • Transfer time: 42 TB over a 1 Gbit/s link at 50% sustained utilization is 42 x 8000 / (1 x 0.5 x 86400), about 7.8 days of paying for both environments and syncing every change.
  • Ongoing cross-cloud traffic: an active-active design sending 500 GB a day between providers costs 500 x 30 x €0.08 = €1,200 a month, for as long as the architecture exists.

The one-off line rarely blocks an exit; the transfer window does. The monthly line is the price of running on two providers by design, a different decision from keeping the ability to leave.

Pragmatic Portability: Spend Where It Pays

Buy portability where the cost to leave is high or unbounded. Accept lock-in where it is bounded and the managed service saves you work every week.

Kubernetes for workloads, not as a promise

Deployments, Services and ConfigMaps move between conformant clusters. Ingress annotations, storage classes and workload identity do not, so keep them in a thin per-environment overlay (Kustomize or Helm values).

Terraform modules with a stable interface

HCL looks the same on every provider; the resources are not. What helps is a module boundary per component (network, cluster, database) with provider-specific code inside and stable outputs, so a second target is a new implementation, not a rewrite of every caller:

hcl
module "orders_db" {
  source = "./modules/postgres/managed" # or ./modules/postgres/self-managed
  name   = "orders"
  tier   = "medium"
}

# callers only ever depend on this output
output "orders_db_host" {
  value = module.orders_db.host
}

Whether you write it in HCL or a general-purpose language matters less than the boundary; our Terraform vs Pulumi comparison covers that choice, and designing module interfaces that survive a provider change is core Terraform consulting work.

Open formats and portable identity

Keep a copy of critical data in a format any target can read, and restore it on a schedule: a directory-format PostgreSQL dump for databases, Parquet for analytics data.

bash
pg_dump --format=directory --jobs=4 --file=/backup/orders "$DATABASE_URL"

Staff sign in through your own identity provider, federated into each cloud over SAML or OIDC; CI uses OIDC federation instead of long-lived keys. Policies will not translate, so document the intent (which service reads which data) in the repository.

Where the abstraction costs more than the lock-in

Say self-managing PostgreSQL to avoid a managed service costs half a day a week in patching, backups and failover drills: about 25 engineer-days a year. Say moving a managed PostgreSQL to another provider is a bounded 15-day project you may never need. Paying 25 days a year to insure against a possible 15-day project is a bad trade. The same logic rules out home-grown abstractions over several providers' queues. Spend the effort where the cost to leave is unbounded: proprietary data models and business logic in workflow glue.

When a Second Provider Is Justified

A second provider earns its cost when:

  • Regulation or data residency requires data or processing in a country where your primary provider has no region.
  • A customer or contract demands it: deployment in the customer's own cloud tenancy, or a public tender that specifies a provider you do not use.
  • An acquisition brings a company that runs elsewhere; integrate identity first, consolidate on a schedule.
  • A capability only one provider has is central to your product; isolate it behind an interface you own.

It does not earn its cost for "avoiding lock-in" in general (an exit plan and a tested rebuild do that more cheaply), for resilience while you still run in a single region (add a region first), or for negotiating leverage (splitting spend weakens the commitment you can offer either provider). If you are still choosing your primary, our comparison of AWS, Azure and GCP covers the criteria.

The European Angle: DORA and the EU Data Act

If you are an EU financial entity, the Digital Operational Resilience Act (DORA) has applied since 17 January 2025 and requires exit strategies for critical ICT third-party providers. Your cloud provider is likely one of them. An exit strategy is not a second provider running in parallel; it is a documented, tested way out, and the inventory, plan and test above are its substance. If you sell to financial entities, expect their exit planning to reach you as contract clauses.

The EU Data Act has applied since 12 September 2025. It obliges cloud providers to make switching possible, limits switching charges to cost during a transition period, and abolishes them from 12 January 2027. That reduces what a provider can charge you to leave. It does nothing for your engineering cost: IAM policies, serverless glue and hard-coded account IDs are as hard to move as before.

Your First Month

  • Week 1: export the bill, list your Terraform state, build the inventory with a cost to leave per entry.
  • Week 2: write version one of the exit plan and give it an owner.
  • Week 3: run the portability test on staging and log every manual step.
  • Week 4: fix the top findings, run the egress arithmetic on your real volumes, and decide whether anything justifies a second provider.

Our cloud migration and infrastructure teams can do that first pass with you. Request a free lock-in assessment: we review your billing export, Terraform state and backup encryption, estimate the cost to leave per layer, run the egress arithmetic on your volumes and, if you are a financial entity, check your exit plan against what DORA asks for.