Cross-cloud migration, AWS ↔ Azure ↔ GCP ↔ OCI, is driven by cost optimization, lock-in avoidance, M&A consolidation, or compliance. Base compute rates are similar across hyperscalers, so savings usually come from rightsizing, committed-use discounts, and avoiding egress, not the move itself. Be clear on your motive before committing, it shapes the whole plan.
Motive first, mechanics second
The single most useful thing you can do before a cross-cloud move is write down why. An enterprise Microsoft agreement, a post-acquisition consolidation onto one provider, a compliance requirement, or a genuine commercial-terms advantage are all real reasons, and each one shapes scope differently. What almost never justifies the move on its own is the sticker price of a VM, because on-demand compute is broadly comparable between the major clouds. The savings that do exist show up afterward, once you rightsize instances, commit to reserved or savings-plan pricing, and stop paying to shuffle data across a boundary. Note the irony worth planning around: the committed-use discounts that make one cloud cheaper are themselves a form of lock-in, so you are trading one commitment for another rather than escaping commitment altogether.
The managed-service layer is the real work
Lifting VMs between clouds is mechanical and mostly a solved problem. The difficulty, and where the lock-in actually lives, is the managed-service layer: databases, queues, serverless, identity, and storage. Some of these map cleanly, some need a data-model rewrite, and identity models rarely translate line by line. Build a mapping table before you move anything, marking each service as mechanical or a re-architecture, so the true size of the project is visible on a spreadsheet rather than discovered mid-cutover.
Lead with the landing zone
Build the target-cloud landing zone first: accounts/subscriptions, networking (VPC/VNet), IAM, and guardrails. Establish connectivity (VPN/Direct Connect/ExpressRoute) to the source for replication. Inventory source resources tagged by application, and map each managed service (databases, queues, serverless, storage) to its target equivalent, the managed-service layer is where lock-in and rework concentrate.
Use the native migration services
- Servers: AWS MGN, Azure Migrate, or Google Migrate to Virtual Machines (agent-based replication).
- Databases: AWS DMS / Azure Database Migration Service (full load + CDC).
- Storage:
aws s3 sync/azcopy/gsutil(mind egress costs).
Re-platform infrastructure as code (Terraform/Bicep/CloudFormation) rather than clicking it twice.
The switch, and confirming it held
Final delta sync, freeze source writes, switch DNS/traffic to the target, validate apps, integrations, and cost, then run a hypercare window with the source as fallback. Test DNS failover and an IaC apply on the target during pre-checks.
Watch the egress
Large data transfers between clouds incur egress fees, factor them into both the plan and the business case. For steady-state workloads, also model whether the target’s committed-use pricing actually beats the source.
Pilot one app group before the rest
Replicate a pilot workload + database end-to-end and validate before scaling. Keep replication tasks until cutover is signed off. The acceptance bar is functional and integration parity, performance at or above baseline, and validated cost, not simply that the VMs booted on the target. Migrate app group by app group rather than big-bang, and keep the source environment as a fallback through a hypercare window, so a rollback is a DNS change rather than a re-migration.
Use the TCO calculator to model a per-vCPU comparison.