A Practical Framework for Cloud Migration
A practical cloud migration framework starts with an honest inventory of what you actually run - applications, their dependencies, and which components hold state - before any strategy gets chosen. Rather than picking one approach for an entire estate, each application is matched individually to one of six paths: rehost, replatform, refactor, repurchase, retire, or retain. Migration then proceeds in ordered waves, starting with lower-risk, lower-dependency systems, with a rollback path kept open at every step. Data cutover uses phased or blue-green techniques to protect consistency, and once live, cloud spend needs ongoing governance rather than a one-time budget, since usage-based billing behaves nothing like a fixed on-premises cost.
Key takeaways
- Assess before you move: build an application inventory, map dependencies, and separate stateful from stateless components before choosing any migration strategy.
- The 6 Rs - rehost, replatform, refactor, repurchase, retire, retain - apply per application, not to an entire portfolio at once.
- Rehosting moves fast but preserves existing technical debt; refactoring enables cloud-native benefits at higher cost and risk.
- Sequence the migration in waves, starting with low-dependency systems, and keep a rollback path open until cutover is verified.
- Cloud cost is usage-based and needs active, ongoing governance - a different discipline than the fixed budgeting on-premises hosting allowed.
Moving an application estate to the cloud is often framed as a single technical decision - "we're moving to AWS" or "we're moving to Azure" - when in practice it's a portfolio of dozens of smaller decisions, each with its own risk profile, cost implication, and failure mode. Organizations that treat it as one decision tend to apply one strategy uniformly across everything they run, which is usually the first mistake: a monolithic core system, a stateless web front end, and an aging batch job with no active maintainer have almost nothing in common, and migrating them the same way ignores exactly the differences that matter.
This guide lays out a framework for approaching migration deliberately: how to assess what you actually run before deciding anything, how to choose a strategy per application using the well-established "6 Rs" model, how to sequence the work so risk is contained rather than concentrated, where containerization genuinely helps versus where it adds unnecessary complexity, how to handle the data cutover itself, and what changes operationally once workloads are actually running in the cloud.
Start with an honest assessment, not a target architecture
It's tempting to begin a migration by picking the target cloud platform and services first - which is a natural instinct, since that's the exciting part - but the more useful starting point is a clear-eyed inventory of what currently exists. A target architecture designed before the estate is understood tends to be designed for an idealized version of the system, not the one actually running in production, and the gap between the two is where migrations run into trouble.
Build a real application inventory
The first step is deceptively simple to describe and often surprisingly hard to execute: list every application, service, and scheduled job currently in production, along with who owns it, what it does, how critical it is to the business, and how actively it's maintained. Many organizations discover during this exercise that their actual inventory is larger and messier than anyone believed - shadow systems built by a since-departed team member, cron jobs nobody remembers the purpose of, and internal tools that quietly became load-bearing years after they were built as a quick fix.
A useful inventory captures, at minimum:
- What the application does and which business function depends on it.
- Who owns it today - a specific team or person, not "IT in general."
- How it's currently hosted - physical servers, virtual machines, a specific data center, a colocation provider.
- How critical it is, ideally on a simple scale, so later prioritization has something concrete to reference.
- How actively it changes - a system with weekly deploys has different constraints than one nobody has touched in three years.
Skipping this step, or doing it superficially, is one of the most common reasons migrations discover expensive surprises mid-project rather than during planning, when surprises are cheap to absorb.
Map dependencies before you map anything else
Once the inventory exists, the next question is how the pieces connect to each other. Dependency mapping identifies which applications talk to which databases, which services call which APIs, which batch jobs feed which downstream reports, and which systems share infrastructure like a network segment or an authentication provider. This is the step that determines what can move independently and what has to move as a unit.
Dependency mapping tends to surface two categories of connection that are easy to underestimate from a distance: implicit dependencies (a shared file system, a cron job that assumes a particular server's local disk layout, a hardcoded IP address instead of a service name) and organizational dependencies (a system that's technically independent but whose only expert is unavailable for the migration window). Both categories cause more schedule slippage than the well-documented, obvious dependencies that show up in an architecture diagram.
Separate stateful from stateless components
A distinction that shapes almost every later decision is whether a given component is stateless (it can be recreated, scaled, or replaced without losing anything, because it holds no durable data of its own) or stateful (it owns data - a database, a file store, a message queue with undelivered messages - that has to be preserved and migrated carefully).
Stateless components are, generally, the easier and lower-risk part of a migration: a web server or an API layer that reads and writes to an external data store can often be redeployed in the cloud, tested, and cut over with a comparatively simple process, because there's no data consistency problem to solve. Stateful components carry the real complexity of a migration - they're where data cutover strategy, consistency guarantees, and rollback planning actually matter, and they deserve disproportionate planning attention relative to how much of the codebase they represent.
The 6 Rs: choosing a strategy per application, not per estate
The most widely used framework for categorizing migration approaches groups strategies into six options, often called the "6 Rs." The value of the framework isn't picking a favorite from the list - it's using it as a structured way to make a distinct, justified decision for each application, rather than defaulting to a single approach for everything.
| Strategy | What it means | Speed | Effort / risk | Effect on technical debt |
|---|---|---|---|---|
| Rehost | Move the application as-is onto cloud infrastructure (e.g., a virtual machine equivalent) | Fast | Low effort, low risk | None - debt moves with the application unchanged |
| Replatform | Make targeted changes (e.g., swap a self-managed database for a managed service) without rearchitecting | Moderate | Moderate effort, moderate risk | Partial - some operational debt is removed |
| Refactor / re-architect | Redesign the application to use cloud-native patterns (containers, managed services, event-driven design) | Slow | High effort, higher risk | Significant reduction, but only where it's actually done well |
| Repurchase | Replace the application with a commercial or SaaS alternative | Varies | Moderate effort, migration and data-conversion risk | Debt is eliminated by retiring the system entirely |
| Retire | Decommission the application because it's no longer needed | Fast | Low effort once confirmed unused | Debt is removed by deletion |
| Retain | Leave the application where it is, for now | None | No migration risk, but on-premises costs and constraints continue | Debt is unchanged and unaddressed |
None of these is inherently correct, and a mature migration plan almost always uses several of them across a single estate. What matters is an honest evaluation of each application against a few practical questions: how much business value would refactoring actually unlock, how much time and budget is realistically available, how much risk the business can tolerate for this specific system, and how much longer this application is expected to remain in active use at all.
The honest tradeoffs of each strategy
Rehosting ("lift and shift") is attractive because it's the fastest path to getting out of an on-premises data center, and for applications under time pressure - an expiring data center contract, aging hardware close to failure - speed can be the deciding factor. The honest cost is that rehosting changes almost nothing about the application itself: whatever technical debt, scaling limitations, or architectural constraints existed before the move exist identically afterward, just running on different infrastructure. Teams sometimes expect a rehost to deliver cloud-native benefits like elastic scaling or resilience, and are surprised when it doesn't, because those benefits come from how an application is built, not from where it runs.
Replatforming sits between rehosting and refactoring - it makes specific, contained improvements (moving a self-managed database to a managed database service, for example) without touching the application's core architecture. It's a reasonable middle path when a full re-architecture isn't justified by the timeline or budget, but a pure lift-and-shift would leave clearly avoidable operational burden in place. The risk is scope creep: a "light" replatform can quietly expand into a much larger effort once the team starts pulling at one thread.
Refactoring / re-architecting genuinely unlocks the properties people associate with cloud computing - independent scaling of components, resilience through redundancy, faster deployment cycles, the ability to adopt managed services instead of operating your own. It's also the most expensive and highest-risk path, because it involves redesigning working software, which introduces the possibility of reintroducing bugs that years of production use had already ironed out of the original system. Refactoring is worth the investment for applications that are strategically important, expected to remain in use for years, and currently constrained by their own architecture rather than by outdated infrastructure - it's a poor use of budget for a system nearing the end of its useful life.
Repurchasing trades a maintenance burden for a subscription and a vendor relationship. It's often the most effective option for commodity functions - a generic system that a mature SaaS product already handles well - because it removes the ongoing cost of running and updating custom software that isn't actually a differentiator for the business. The tradeoff is a new form of dependency: on the vendor's roadmap, pricing, and data model, plus the one-time cost and risk of migrating data into an unfamiliar system.
Retiring applications that are no longer needed - or whose function has been absorbed by another system - is frequently underused simply because nobody has revisited the question in years. Every application removed from the inventory before migration begins is one fewer system to plan, test, and support afterward, which makes a retirement review one of the highest-value early steps in the whole process.
Retaining an application on-premises, at least temporarily, is a legitimate outcome, not a failure to migrate. Compliance constraints, a licensing agreement tied to specific hardware, or a system scheduled for retirement within the migration window can all make "not yet" the responsible answer for a specific application, as long as that decision is deliberate and documented rather than a default born of running out of time to plan it properly.
Choosing per application, not for the whole portfolio
The most common estate-level mistake is applying one strategy across the board - usually "rehost everything" under time pressure, or "refactor everything" out of enthusiasm for cloud-native patterns without weighing the cost against each application's actual importance. Neither extreme reflects the reality that a typical estate contains applications with meaningfully different value, lifespan, and constraints.
A workable approach is to score each application against a small number of factors - business criticality, expected remaining lifespan, current pain points (is the problem the infrastructure, or the architecture?), available budget and timeline, and dependency complexity - and let that scoring point toward a strategy rather than starting from a preferred answer. Applications that are critical, long-lived, and architecturally constrained are strong refactor candidates. Applications that are important but architecturally sound, just running on aging hardware, are strong rehost or replatform candidates. Applications that are marginal, redundant, or near end-of-life are retire or retain candidates, and applications solving a well-understood, non-differentiating problem are repurchase candidates.
Sequencing: waves, risk ordering, and a rollback path
Even with a strategy chosen per application, the order of execution matters as much as the strategies themselves. A migration that tries to move everything simultaneously concentrates risk, overloads the team's capacity to respond to problems, and makes it much harder to isolate the cause when something does go wrong.
A wave-based sequence generally works better than a single cutover event:
- Start with low-risk, low-dependency systems. Internal tools, systems with few integrations, and applications where an outage would be inconvenient rather than damaging are the right place to build operational experience with the new environment before anything critical is on the line.
- Use early waves to validate tooling and runbooks, not just to move applications. The monitoring setup, deployment pipeline, incident response process, and access controls all need to prove themselves under real conditions before a business-critical system depends on them.
- Group tightly coupled systems into the same wave. If dependency mapping showed that two applications share a database or communicate synchronously and frequently, splitting them across waves usually introduces more complexity (temporary bridging, dual-write logic) than it saves.
- Save the most critical, most complex systems for later waves, once the team has direct experience with the platform and the process, not because they're less important, but because that's when the team is most capable of handling them well.
- Keep a rollback path available at each stage, particularly for stateful systems. That typically means keeping the source environment running and in sync (or restorable) for a defined window after each cutover, so a serious problem discovered after go-live can be reversed rather than requiring an emergency fix under pressure in an unfamiliar environment.
The rollback path is worth planning explicitly rather than assuming it will be improvised if needed - by the time a rollback is actually necessary, there usually isn't time left to design one from scratch.
Where containerization fits - and where it doesn't
Docker and Kubernetes come up in almost every migration conversation, and it's worth separating the two, because they solve different problems and carry different amounts of justified complexity.
Docker, used to package an application and its dependencies into a portable, consistent unit, is close to a default good practice during migration regardless of target strategy. It removes a large class of "works on one machine, not another" problems, makes an application's runtime dependencies explicit rather than implicit in a server's configuration history, and gives a replatform or refactor effort a clean, testable unit to work with. The cost of adopting Docker for packaging is comparatively low even for a team new to it.
Kubernetes, used to orchestrate many containerized services - handling scheduling, scaling, service discovery, and self-healing across a fleet of containers - is a different scale of decision. It earns its operational complexity when there are enough independently deployed services, with variable enough load and frequent enough deployment, that manual or simpler orchestration would become a genuine bottleneck. For an estate with a handful of applications, predictable traffic, and a small team without existing Kubernetes experience, adopting it during a migration adds a real learning curve and operational burden that a managed container service, or even a modest number of virtual machines running Docker directly, would avoid while delivering the same practical outcome.
A reasonable rule of thumb: use Docker for packaging as a near-default, and treat Kubernetes as a decision that should be justified by actual scale and deployment frequency, not adopted because it's the pattern everyone else describes using.
Data migration and cutover: protecting consistency
Moving code is usually the more straightforward half of a migration; moving data safely, without loss or corruption, and cutting traffic over to the new environment without a damaging outage, is where the harder engineering problems live.
A few cutover patterns cover most situations:
- Big-bang cutover: replicate data once, take a maintenance window, redirect traffic to the new environment. Simplest to plan, but requires accepting downtime and carries the highest risk if something is wrong with the new environment, since there's no gradual validation period.
- Phased cutover: move one data domain or one set of users at a time, validating each phase before moving to the next. Reduces risk by limiting the blast radius of any single phase, at the cost of running a more complex, longer transition period where some data may temporarily exist in both places.
- Blue-green cutover: keep the old ("blue") and new ("green") environments both fully running, direct a small or controlled slice of traffic to the new environment, and shift the rest over once it's proven stable - with the ability to shift back quickly if a problem appears. This gives the fastest, lowest-risk rollback of the three patterns, but requires running two full environments simultaneously for a period, which has a real infrastructure and coordination cost.
Whichever pattern is used, the central risk to manage is data drift - the source and target systems diverging because both can accept writes during the transition window. The safest approaches either freeze writes to the source system during final synchronization, or design cutover so each piece of data moves exactly once and ownership transfers cleanly, rather than allowing an extended period where the same data could be modified in two places at once. Reconciling divergent data after the fact is materially harder than preventing the divergence in the first place, which is why this deserves explicit planning rather than being treated as a detail to work out during the cutover itself.
Cost governance doesn't end at go-live - it starts there
One of the more consequential differences between on-premises and cloud infrastructure is how costs behave. An on-premises footprint is largely a fixed cost once hardware is purchased - it doesn't change month to month based on how much it's used. Cloud infrastructure is usage-based by design: pay for compute, storage, data transfer, and managed services in proportion to what's actually consumed, which means cost is now a variable that responds to code changes, traffic patterns, and configuration decisions in ways a fixed on-premises budget never did.
This has a direct practical consequence: cost management has to become an ongoing operational discipline, not a one-time line item in the migration budget. Left unmanaged, cloud costs tend to grow through a predictable set of causes - resources provisioned for a migration and never resized afterward, development or test environments left running continuously instead of being shut down outside working hours, data transfer costs between regions or services that weren't accounted for during design, and storage that accumulates indefinitely because nobody owns a retention policy.
Practical governance measures worth putting in place from the start, rather than retrofitting after costs have already grown:
- Tagging and cost allocation so spend can be attributed to a specific application, team, or environment, making it possible to identify where cost is actually concentrated instead of only seeing a single aggregate bill.
- Rightsizing reviews on a regular cadence, since the resource sizes chosen during migration are frequently based on conservative estimates that turn out to be larger than what's actually needed once real usage patterns are visible.
- Autoscaling for variable workloads, so capacity (and cost) tracks actual demand rather than being provisioned for peak load around the clock.
- Budget alerts and spend anomaly detection, so an unexpected cost spike - a misconfigured job, a runaway process, an accidental data transfer between regions - is caught within days, not discovered at the end of a billing cycle.
- A defined owner for cloud cost, since usage-based spend without clear ownership tends to be everyone's concern in principle and nobody's responsibility in practice.
Migrating without building this discipline in from the start is one of the more common ways a project that looked financially sound in planning ends up costing more than the on-premises footprint it replaced - not because the cloud is inherently more expensive, but because nobody was actively managing a cost structure that behaves fundamentally differently from what came before it.
Common failure modes worth planning around
A handful of failure patterns recur across migrations enough to be worth naming explicitly, so they can be planned against rather than discovered the hard way.
Migrating without addressing the underlying architecture. A rehost that moves a struggling application onto cloud infrastructure without changing anything about it will, predictably, keep struggling - just in a new location, and now subject to a usage-based bill for the privilege. Rehosting is a legitimate strategy for the right applications, but it's not a substitute for architectural work when the architecture itself is the actual problem.
Treating migration as a one-time project rather than an ongoing operational shift. A project plan with a defined end date is useful for organizing the migration effort itself, but the operational model afterward - monitoring, cost governance, security patching, incident response, capacity planning - is a permanent responsibility, not a phase that concludes when the last application goes live. Teams that disband immediately after cutover, treating "migration complete" as "work complete," tend to accumulate the same kind of neglect-driven problems in the cloud that motivated leaving the old environment in the first place.
Underestimating data-transfer time and cutover complexity. Moving large volumes of data over a network takes real, sometimes substantial, time, and that time has to be planned into the schedule rather than assumed away. Teams that plan a cutover window based on how long the application deployment takes, without separately accounting for how long the data replication and validation will take, frequently find the two don't align, forcing an uncomfortable choice between extending a maintenance window or cutting over before validation is actually complete.
Skipping dependency mapping because it feels like overhead. Every one of the failure modes above tends to trace back, at least partly, to an incomplete picture of how the estate actually fits together. The time spent mapping dependencies thoroughly before migration begins is consistently smaller than the time spent resolving an unexpected dependency discovered mid-migration, which is why this step deserves real investment rather than being treated as a formality on the way to the more visible technical work.
Taken together, these failure modes point to the same underlying principle that runs through this whole framework: cloud migration is a portfolio of individual, well-informed decisions - about each application's strategy, about sequencing, about data handling, about ongoing operations - rather than a single technical event with one right answer applied uniformly across everything an organization runs.
Frequently asked questions
Should we migrate everything to the cloud at the same time?
No - a single cutover event across an entire application estate concentrates risk in a way that's rarely necessary and hard to recover from if something goes wrong. A wave-based approach, where lower-risk and lower-dependency systems move first, gives the team a chance to build operational confidence and refine runbooks before touching the applications where a mistake would be most costly. The exceptions are systems so tightly coupled that they only make sense to move together, which is exactly why dependency mapping has to happen before sequencing is decided, not after.
How do we decide between rehosting and refactoring a given application?
Start from the application's expected lifespan and its current pain points, not from a general preference for one strategy. If the system is stable, has years of useful life left, and its main problem is aging on-premises infrastructure rather than the software architecture itself, rehosting removes the infrastructure problem quickly without adding project risk. If the application's architecture is itself the constraint - it can't scale, can't be deployed independently, or actively blocks the business from shipping changes - refactoring addresses the actual bottleneck, and a rehost would just move the same limitations onto a different set of servers.
When does Kubernetes actually make sense during a migration, and when is it overkill?
Kubernetes earns its complexity when you have multiple services that need independent scaling, frequent deployments, and workloads variable enough that dynamic scheduling produces a real efficiency gain - typically once an estate reaches a double-digit number of services under active development. For a handful of applications, for workloads with predictable and modest traffic, or for a team without existing container-orchestration experience, a managed container service or even a small number of long-running virtual machines will do the same job with far less operational overhead. Docker for packaging is close to a default choice; Kubernetes for orchestration is a decision that should be justified by actual scale, not adopted because it's the expected next step.
What's the biggest risk during data cutover specifically?
Data drift between the old and new environments during the window when both are technically capable of accepting writes. If users or integrations can write to the source system after replication to the target has started, the two data sets diverge, and reconciling that divergence after the fact is far harder than preventing it in the first place. The safer patterns - a hard write freeze during final sync, or a phased cutover where each data domain moves once and only once - both accept a short availability cost specifically to avoid this problem, and that tradeoff is usually worth making deliberately rather than being discovered under pressure mid-cutover.
Does cloud migration reduce our infrastructure costs?
Not automatically, and assuming it will is one of the more common planning mistakes. A pure rehost of an inefficiently sized on-premises footprint often costs about the same or more in the cloud once usage-based pricing is applied to old habits like over-provisioned servers left running around the clock. Cost benefits usually come from actions taken after migration - rightsizing instances, adopting autoscaling, moving suitable workloads to spot or reserved capacity, and shutting down idle resources - not from the act of moving infrastructure alone. Cost governance has to be treated as an ongoing operational practice, not a side effect of the migration itself.
Related reading
- DevOps & Cloud InfrastructureDevOps engineering for teams with manual, risky deployments: CI/CD, infrastructure-as-code, and observability built as a real discipline.
- Legacy System ModernizationAudit-led legacy system modernization with incremental, strangler-fig migration and phased cutover for companies across Sweden and the Nordic region.
- AWSHow North Tech Labs uses AWS for cloud infrastructure — real capabilities, genuine trade-offs, and when a different platform or approach fits better.
- Microsoft AzureHow North Tech Labs uses Microsoft Azure as a secondary cloud platform to AWS — where it genuinely fits, and when another platform suits a project better.
- DockerHow North Tech Labs uses Docker for reproducible containers — real strengths, honest trade-offs, and where it fits in a deployment architecture.
- KubernetesHow North Tech Labs uses Kubernetes for container orchestration at scale — real capabilities, real operational cost, and when a simpler approach fits better.
Have a specific question about your project?
This article covers the general pattern — the fastest way to get a specific answer is to ask us directly.