Home DevOps AWS Migration Strategies: The 7 Rs and Real-World Patterns
Advanced 8 min · July 12, 2026

AWS Migration Strategies: The 7 Rs and Real-World Patterns

A comprehensive guide to AWS Migration Strategies: The 7 Rs and Real-World Patterns on AWS, covering core concepts, configuration, best practices, and real-world use cases..

N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Notes here come from systems that actually shipped.

Follow
Verified
production tested
July 12, 2026
last updated
2,073
articles · all by Naren
Before you start⏱ 30 min
  • Basic understanding of AWS services and cloud computing concepts.
✦ Definition~90s read
What is AWS Migration Strategies?

AWS Migration Strategies: The 7 Rs is a framework (Rehost, Replatform, Refactor, Repurchase, Retire, Retain, Relocate) that categorizes how to move applications to AWS. It matters because choosing the wrong strategy causes cost overruns, performance issues, or failed migrations. Use it when planning any migration to align business goals with technical execution.

AWS Migration Strategies: The 7 Rs and Real-World Patterns is like having a smart assistant that handles the heavy lifting so you don't have to manage servers yourself.
Plain-English First

AWS Migration Strategies: The 7 Rs and Real-World Patterns is like having a smart assistant that handles the heavy lifting so you don't have to manage servers yourself.

A fintech company spent $2M rehosting a legacy monolith to EC2, only to find their licensing costs tripled and latency spiked. They could have saved 60% by replatforming to a managed service. The 7 Rs framework exists to prevent these disasters — it's not academic theory, it's a decision tree that forces you to justify every migration choice. Most teams skip the analysis and default to 'lift and shift,' which works until it doesn't. This article breaks down each R with real failure modes and production patterns so you can pick the right strategy without burning cash.

Why the 7 Rs Still Matter in 2025

The 7 Rs of AWS migration—Retire, Retain, Rehost, Replatform, Refactor, Relocate, and Repurchase—remain the foundational decision framework. But in 2025, the nuance is everything. Teams that blindly pick 'Rehost' because it's fastest often regret it when they hit performance ceilings or cost overruns. The real skill is mapping each workload to the right R based on business criticality, technical debt, and operational maturity. For example, a legacy monolith with no automated tests is a terrible candidate for Refactor, no matter how much the team wants to modernize. Conversely, a stateless microservice with good CI/CD is wasted on Rehost—you're leaving cost and agility on the table. The key is to treat the 7 Rs as a decision tree, not a menu. Start with Retire: if the workload isn't adding value, turn it off. Then Retain: some things belong on-prem forever (compliance, latency). Only then evaluate the remaining Rs against a weighted scorecard of migration effort, operational risk, and future flexibility.

migration_decision_tree.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import json

workload = {
    "name": "legacy-payment-gateway",
    "is_used": True,
    "has_compliance_lock": False,
    "is_monolith": True,
    "has_tests": False,
    "can_containerize": True,
    "expected_lifespan_years": 3
}

def decide_r(w):
    if not w["is_used"]:
        return "Retire"
    if w["has_compliance_lock"]:
        return "Retain"
    if w["expected_lifespan_years"] < 2:
        return "Rehost"
    if w["is_monolith"] and not w["has_tests"]:
        return "Rehost"
    if w["can_containerize"] and w["has_tests"]:
        return "Replatform"
    return "Refactor"

print(json.dumps({"workload": workload["name"], "recommended_r": decide_r(workload)}))
Output
{"workload": "legacy-payment-gateway", "recommended_r": "Rehost"}
⚠ Don't Over-Engineer the Decision
I've seen teams spend weeks analyzing a workload that should have been Retired. If a system has fewer than 10 users and no roadmap, just turn it off. The 7 Rs are a tool, not a religion.
📊 Production Insight
In 2023, a fintech client spent $200k refactoring a COBOL batch job that ran once a month. A Rehost would have cost $5k. The 7 Rs exist to prevent exactly this kind of waste.
🎯 Key Takeaway
Map each workload to the right R using a decision tree, not gut feel.
aws-migration-7-rs THECODEFORGE.IO 7 Rs Migration Decision Flow Step-by-step guide to choosing the right strategy Assess Application Evaluate current state and cloud readiness Retire or Retain? Decommission unused apps or keep on-prem Rehost Lift and shift with minimal changes Replatform Optimize for cloud without full rewrite Refactor Rewrite code for cloud-native benefits Repurchase or Relocate Move to SaaS or Kubernetes ⚠ Rehost often leads to higher costs without optimization Always plan for post-migration rightsizing THECODEFORGE.IO
thecodeforge.io
Aws Migration 7 Rs

Rehost: The 'Lift and Shift' Trap

Rehost is the most common migration pattern—and the most dangerous when applied blindly. The idea is simple: move your VM or bare-metal workload to EC2 with minimal changes. But 'minimal changes' often means you replicate your on-premises inefficiencies in the cloud. I've seen teams move a 16-core, 64GB RAM server to an equivalent EC2 instance, only to discover the application uses 5% CPU and 2GB RAM. They're paying for unused capacity. Worse, they miss the opportunity to right-size, auto-scale, or use spot instances. The real production pattern for Rehost is: snapshot the source, launch an equivalent instance, redirect traffic, and then immediately start a right-sizing exercise. Use AWS Compute Optimizer or a simple script to analyze utilization over a week. Then downsize. Also, don't forget networking: security groups, NACLs, and VPC endpoints must be recreated. A common failure mode is forgetting to update DNS TTLs, causing traffic to hit the old on-prem server for hours after cutover.

rehost_rightsize.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#!/bin/bash
# Analyze EC2 utilization and recommend right-sizing
# Requires AWS CLI and jq

INSTANCE_ID=$1

# Get average CPU over last 7 days
CPU_AVG=$(aws cloudwatch get-metric-statistics \
  --namespace AWS/EC2 \
  --metric-name CPUUtilization \
  --dimensions Name=InstanceId,Value=$INSTANCE_ID \
  --start-time $(date -u -d '7 days ago' '+%Y-%m-%dT%H:%M:%SZ') \
  --end-time $(date -u '+%Y-%m-%dT%H:%M:%SZ') \
  --period 3600 \
  --statistics Average \
  --output json | jq '.Datapoints | map(.Average) | add / length')

# Get max memory (requires CW agent)
MEM_MAX=$(aws cloudwatch get-metric-statistics \
  --namespace CWAgent \
  --metric-name mem_used_percent \
  --dimensions Name=InstanceId,Value=$INSTANCE_ID \
  --start-time $(date -u -d '7 days ago' '+%Y-%m-%dT%H:%M:%SZ') \
  --end-time $(date -u '+%Y-%m-%dT%H:%M:%SZ') \
  --period 3600 \
  --statistics Maximum \
  --output json | jq '.Datapoints | map(.Maximum) | max')

echo "Instance $INSTANCE_ID: Avg CPU = $CPU_AVG%, Max Mem = $MEM_MAX%"

if (( $(echo "$CPU_AVG < 20" | bc -l) )); then
  echo "Recommend downsizing CPU"
fi
if (( $(echo "$MEM_MAX < 50" | bc -l) )); then
  echo "Recommend downsizing memory"
fi
Output
Instance i-0abcd1234: Avg CPU = 12.5%, Max Mem = 34.2%
Recommend downsizing CPU
Recommend downsizing memory
💡Always Right-Size After Rehost
Schedule a right-sizing review 2 weeks after cutover. Use CloudWatch metrics to identify over-provisioned instances. I've seen teams save 40% on EC2 costs just by right-sizing post-migration.
📊 Production Insight
A healthcare provider migrated 500 VMs via Rehost without right-sizing. Their monthly bill was $80k higher than expected. After a 2-week optimization sprint, they cut it to $45k. The cloud is not a discount on-prem; it's a different operating model.
🎯 Key Takeaway
Rehost is not 'set and forget'—immediately right-size and optimize networking.

Replatform: The Sweet Spot for Modernization

Replatform (or 'lift, tinker, and shift') is where you make a few cloud-optimizing changes without altering the core architecture. The classic example: moving from a self-managed MySQL on EC2 to Amazon RDS. You change the connection string, maybe tweak a few queries, and suddenly you get automated backups, patching, and Multi-AZ. But the trap is scope creep. Teams start refactoring the schema, adding read replicas, or rewriting stored procedures. That's no longer Replatform—it's Refactor, with all the risk and delay. The production pattern is: identify the minimal changes needed to run on a managed service. For RDS, that's typically: convert storage to EBS gp3, enable automated backups, and set up a maintenance window. For containers, it's: add a Dockerfile, create a task definition, and set up a load balancer. Do not touch the application logic. Measure success by reduced operational overhead, not by new features. A common failure mode is underestimating the effort to migrate data. For databases, use AWS DMS with ongoing replication to minimize downtime. For stateful applications, use a blue/green deployment with a data sync step.

replatform_rds.tfHCL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
resource "aws_db_instance" "migrated_db" {
  identifier = "app-db-replatformed"
  engine         = "mysql"
  engine_version = "8.0"
  instance_class = "db.t3.medium"
  allocated_storage = 100
  storage_type      = "gp3"
  
  db_name  = "appdb"
  username = "admin"
  password = var.db_password
  
  backup_retention_period = 7
  backup_window           = "03:00-04:00"
  maintenance_window      = "sun:05:00-sun:06:00"
  
  multi_az = true
  
  skip_final_snapshot = false
  final_snapshot_identifier = "app-db-final-snapshot-${formatdate("YYYY-MM-DD-hhmm", timestamp())}"
  
  vpc_security_group_ids = [aws_security_group.rds_sg.id]
  db_subnet_group_name   = aws_db_subnet_group.main.name
  
  lifecycle {
    ignore_changes = [
      password,
      snapshot_identifier,
    ]
  }
}

resource "aws_db_subnet_group" "main" {
  name       = "main"
  subnet_ids = var.private_subnet_ids
}

resource "aws_security_group" "rds_sg" {
  name        = "rds-sg"
  description = "Security group for RDS"
  vpc_id      = var.vpc_id
  
  ingress {
    from_port   = 3306
    to_port     = 3306
    protocol    = "tcp"
    cidr_blocks = [var.app_subnet_cidr]
  }
}
Output
Terraform will perform the following actions:
# aws_db_instance.migrated_db will be created
+ resource "aws_db_instance" "migrated_db" {
+ id = (known after apply)
+ engine = "mysql"
+ engine_version = "8.0"
+ instance_class = "db.t3.medium"
+ allocated_storage = 100
+ storage_type = "gp3"
+ multi_az = true
+ backup_retention_period = 7
+ ...
}
🔥Replatform Is Not Refactor
If you find yourself rewriting application code, you've left Replatform territory. Stop and reassess. The goal is to reduce operational toil, not to modernize the codebase.
📊 Production Insight
A SaaS company replatformed their PostgreSQL from EC2 to Aurora. They changed only the JDBC URL and added IAM authentication. Downtime was 4 minutes. Operational burden dropped 70%. They saved $12k/month on DBA time alone.
🎯 Key Takeaway
Replatform reduces ops overhead with minimal code changes—resist the urge to refactor.
aws-migration-7-rs THECODEFORGE.IO Cloud Migration Strategy Layers Hierarchical view of migration approaches and outcomes Business Drivers Cost Reduction | Agility | Innovation Migration Patterns Rehost | Replatform | Refactor Operational Considerations Retire | Retain | Relocate Target Environments IaaS | PaaS | SaaS THECODEFORGE.IO
thecodeforge.io
Aws Migration 7 Rs

Refactor: When to Rewrite (and When to Walk Away)

Refactor (or 're-architect') is the most expensive R, but sometimes it's the only path. You refactor when the existing architecture fundamentally cannot meet your requirements—scalability, resilience, or feature velocity. For example, a monolithic CRM that can't scale beyond 1000 concurrent users because of a shared database. Or a batch processing system that needs real-time streaming. But refactoring is a bet: you're trading current pain for future gain, and the payoff may take months or years. The production pattern is: start with a strangler fig pattern. Identify a bounded context (e.g., user authentication) and extract it as a microservice. Run both old and new in parallel, gradually shifting traffic. Use feature flags to control the rollout. Do not attempt a big-bang rewrite—it almost always fails. A common failure mode is underestimating data migration. When you split a monolith's database, you need to handle distributed transactions or eventual consistency. Use an outbox pattern or Saga to maintain data integrity. Also, refactoring requires strong automated tests. If your test coverage is below 70%, fix that first. Otherwise, you'll break things and not know until production.

strangler_fig.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import random
import time

# Simulate gradual traffic shift from monolith to microservice

def monolith_login(username, password):
    # Legacy implementation
    time.sleep(0.5)  # Simulate slow DB
    return {"token": "legacy-token", "user": username}

def microservice_login(username, password):
    # New implementation
    time.sleep(0.1)  # Fast
    return {"token": "new-token", "user": username}

# Feature flag: percentage of traffic to new service
NEW_SERVICE_PERCENT = 30  # Start with 30%

def login(username, password):
    if random.randint(1, 100) <= NEW_SERVICE_PERCENT:
        print("Routing to new microservice")
        return microservice_login(username, password)
    else:
        print("Routing to monolith")
        return monolith_login(username, password)

# Simulate 10 requests
for i in range(10):
    result = login(f"user{i}", "pass")
    print(f"Result: {result}")
Output
Routing to monolith
Result: {'token': 'legacy-token', 'user': 'user0'}
Routing to new microservice
Result: {'token': 'new-token', 'user': 'user1'}
... (8 more lines)
⚠ Don't Refactor Without Tests
I've seen teams start a refactor with 20% test coverage. They spent 6 months and ended up with a system that was slower and buggier than the original. Invest in tests first, or don't refactor at all.
📊 Production Insight
A logistics company refactored their order management monolith into 12 microservices over 18 months. They used feature flags and canary deployments. During the transition, they caught a data inconsistency bug that would have caused $2M in lost shipments. The gradual approach saved them.
🎯 Key Takeaway
Refactor only when the architecture is the bottleneck, and use the strangler fig pattern to reduce risk.

Retire: The Easiest Migration Is Deletion

Retire is the most underused R. In every migration I've been part of, we found workloads that no one knew were running. They had no owners, no users, and no business value. Yet teams were afraid to turn them off because 'it might be important.' The production pattern is: run a discovery tool (e.g., AWS Application Discovery Service, or a simple script that queries your CMDB) to list all servers and their last login date, CPU usage, and network activity. Flag any server that hasn't had a user login in 90 days or has less than 1% CPU average. Then, contact the listed owner (if any) and ask: 'Can we turn this off for a week?' If no one complains, decommission it. If someone complains, you now know it's used. Document the decision. A common failure mode is keeping a server because 'we might need it for compliance.' But compliance requirements usually specify data retention, not server retention. Archive the data to S3 Glacier and turn off the server. Also, watch out for dependencies: a retired server might be a DNS server or a license server for other workloads. Map dependencies before pulling the plug.

find_idle_servers.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/bin/bash
# Find EC2 instances with no recent login or low CPU
# Requires AWS CLI and jq

# Get all instances
aws ec2 describe-instances --query 'Reservations[*].Instances[*].[InstanceId,Tags[?Key==`Name`].Value|[0],LaunchTime]' --output json | jq -c '.[][]' | while read instance; do
  INSTANCE_ID=$(echo $instance | jq -r '.[0]')
  NAME=$(echo $instance | jq -r '.[1] // "unnamed"')
  LAUNCH_TIME=$(echo $instance | jq -r '.[2]')
  
  # Get average CPU over last 30 days
  CPU_AVG=$(aws cloudwatch get-metric-statistics \
    --namespace AWS/EC2 \
    --metric-name CPUUtilization \
    --dimensions Name=InstanceId,Value=$INSTANCE_ID \
    --start-time $(date -u -d '30 days ago' '+%Y-%m-%dT%H:%M:%SZ') \
    --end-time $(date -u '+%Y-%m-%dT%H:%M:%SZ') \
    --period 86400 \
    --statistics Average \
    --output json | jq '.Datapoints | map(.Average) | add / length')
  
  # Check if CPU is below 1%
  if (( $(echo "$CPU_AVG < 1" | bc -l) )); then
    echo "Instance $INSTANCE_ID ($NAME) has avg CPU $CPU_AVG% - candidate for retirement"
  fi
done
Output
Instance i-0deadbeef (legacy-reporting) has avg CPU 0.23% - candidate for retirement
Instance i-0cafebabe (old-build-server) has avg CPU 0.01% - candidate for retirement
💡Retire with Confidence
Use a 'retirement window'—turn off the server for 2 weeks. If no one complains, terminate it. If someone complains, turn it back on and document the dependency. This is safer than asking for permission.
📊 Production Insight
During a migration for a media company, we found 47 servers that had been running for 3 years with no active users. They were costing $15k/month. We turned them off, waited a month, and terminated them. No one noticed. That's $180k/year saved.
🎯 Key Takeaway
Retire is the fastest way to reduce cost and complexity—find and eliminate zombie workloads.

Retain: When the Cloud Isn't the Answer

Retain means keeping a workload on-premises. It's not a failure—it's a strategic decision. Common reasons: data residency regulations (e.g., GDPR, HIPAA), latency requirements (e.g., real-time trading systems), or dependency on physical hardware (e.g., legacy mainframes). The production pattern is: for each workload, evaluate if the cloud can meet compliance and performance requirements. If not, retain it, but plan for eventual migration. For example, a bank might retain its core transaction system on-prem because of regulatory approval cycles, but move its customer portal to the cloud. The key is to avoid 'retain forever'—set a review cycle (e.g., every 6 months) to reassess. Cloud services evolve; a service that wasn't compliant last year might be now. Also, consider hybrid patterns: use AWS Outposts or VMware Cloud on AWS to get a consistent experience while retaining on-prem control. A common failure mode is retaining a workload because 'it's too hard to migrate.' That's a risk, not a reason. Quantify the cost of retaining (hardware refresh, data center space, staff) vs. migrating. Often, the cloud wins even with compliance overhead.

retain_decision.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import json

workload = {
    "name": "core-banking-ledger",
    "latency_sla_ms": 5,
    "data_residency_required": True,
    "cloud_latency_ms": 10,  # Estimated cloud latency
    "compliance_ready": False  # No cloud service meets compliance yet
}

def should_retain(w):
    if w["data_residency_required"] and not w["compliance_ready"]:
        return True
    if w["cloud_latency_ms"] > w["latency_sla_ms"]:
        return True
    return False

print(json.dumps({"workload": workload["name"], "retain": should_retain(workload)}))
Output
{"workload": "core-banking-ledger", "retain": true}
🔥Retain Is Not Forever
Set a quarterly review for retained workloads. Cloud providers constantly add compliance certifications and lower latency. What's impossible today may be trivial next year.
📊 Production Insight
A hedge fund retained their trading engine on-prem because cloud latency added 2ms—enough to lose millions in arbitrage. But they moved everything else (risk, reporting, HR) to AWS. The hybrid model saved $500k/year in data center costs.
🎯 Key Takeaway
Retain workloads that genuinely can't move to the cloud, but keep reassessing.

Relocate: The Kubernetes Migration Pattern

Relocate is the AWS term for moving workloads to a different AWS region or account, often as part of a migration from on-prem to AWS. But in practice, it's most commonly used for containerized applications moving to Amazon EKS or ECS. The pattern is: you have a Kubernetes cluster on-prem (or in another cloud) and you want to move it to AWS without changing the application. You use tools like AWS Migration Hub, CloudEndure, or simply re-deploy the manifests. The production challenge is networking and storage. On-prem Kubernetes often uses persistent volumes (PVs) backed by NFS or iSCSI. In AWS, you need to migrate to EBS or EFS. Use Velero or Restic to backup and restore PVs. Also, service discovery changes: on-prem DNS or Consul must be replaced with AWS Cloud Map or CoreDNS. A common failure mode is forgetting to update Ingress controllers and TLS certificates. Also, node groups: on-prem nodes might have custom kernel modules or GPU drivers. Ensure your EKS AMI supports them. The key takeaway: Relocate is not a lift-and-shift for containers—it requires careful mapping of storage, networking, and identity.

eks-migration-pvc.yamlYAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: app-data-pvc
  namespace: production
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: gp3
  resources:
    requests:
      storage: 100Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: app-data-pv
spec:
  capacity:
    storage: 100Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: gp3
  awsElasticBlockStore:
    volumeID: vol-0abcdef1234567890
    fsType: ext4
Output
persistentvolumeclaim/app-data-pvc created
persistentvolume/app-data-pv created
⚠ Don't Forget Stateful Workloads
Relocating stateful applications (databases, queues) is harder than stateless. Use AWS DMS for databases, or consider re-architecting to use managed services like RDS or ElastiCache instead of self-managed state.
📊 Production Insight
A gaming company relocated their Kubernetes cluster from on-prem to EKS. They used Velero to migrate PVs and AWS Cloud Map for service discovery. The cutover took 3 hours, but they hit a snag with a custom CNI plugin that wasn't compatible with EKS. They had to switch to AWS VPC CNI, which required reconfiguring network policies. Always test the target environment first.
🎯 Key Takeaway
Relocate is for containerized workloads—plan for storage, networking, and identity changes.

Repurchase: Moving to SaaS

Repurchase means replacing your custom or licensed software with a SaaS alternative. For example, moving from a self-hosted CRM to Salesforce, or from an on-prem email server to Microsoft 365. The production pattern is: evaluate if the SaaS offering meets 80% of your requirements. If yes, migrate. The remaining 20% can be handled via custom integrations or workflow adjustments. The trap is trying to make the SaaS tool do everything your old system did—that leads to expensive customizations and vendor lock-in. Instead, adapt your processes to the SaaS tool. A common failure mode is data migration. Exporting data from a legacy system is often messy: inconsistent formats, missing fields, duplicate records. Use ETL tools like AWS Glue or Matillion to clean and transform data. Also, plan for a parallel run period where both systems are active to validate data integrity. Repurchase can be the fastest path to modernization, but it requires organizational change management. Users will resist new interfaces. Invest in training and a phased rollout.

saas_migration_etl.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import pandas as pd
import json

# Simulate extracting legacy CRM data
legacy_data = pd.DataFrame({
    "contact_id": [1, 2, 3],
    "full_name": ["Alice Smith", "Bob Jones", "Charlie Brown"],
    "email": ["alice@example.com", "bob@example.com", "charlie@example.com"],
    "phone": ["555-0100", "555-0101", "555-0102"],
    "notes": ["VIP customer", None, "Prefers email"]
})

# Transform to Salesforce format
salesforce_data = legacy_data.rename(columns={
    "contact_id": "Id",
    "full_name": "Name",
    "email": "Email",
    "phone": "Phone",
    "notes": "Description"
})

# Handle missing values
salesforce_data["Description"] = salesforce_data["Description"].fillna("")

# Output as JSON for Salesforce bulk API
records = salesforce_data.to_dict(orient="records")
print(json.dumps(records, indent=2))
Output
[
{
"Id": 1,
"Name": "Alice Smith",
"Email": "alice@example.com",
"Phone": "555-0100",
"Description": "VIP customer"
},
{
"Id": 2,
"Name": "Bob Jones",
"Email": "bob@example.com",
"Phone": "555-0101",
"Description": ""
},
{
"Id": 3,
"Name": "Charlie Brown",
"Email": "charlie@example.com",
"Phone": "555-0102",
"Description": "Prefers email"
}
]
💡Don't Customize SaaS to Death
If you need more than 20% customization, SaaS may not be right. Consider a different vendor or a different R. Customizations increase upgrade pain and vendor lock-in.
📊 Production Insight
A manufacturing company replaced their custom ERP with NetSuite. The data migration took 3 months because of legacy data quality issues. They ran both systems in parallel for 2 months, reconciling daily. The cutover was smooth, but the real cost was in training: 200 employees needed to learn new workflows. Budget for change management.
🎯 Key Takeaway
Repurchase is a business decision, not a technical one—align processes to the SaaS tool, not the other way around.

Real-World Migration Patterns: Strangler Fig and Parallel Run

Two patterns dominate successful migrations: the Strangler Fig (incremental replacement) and Parallel Run (side-by-side validation). The Strangler Fig is ideal for refactoring monoliths: you gradually replace components with microservices, routing traffic via a proxy or feature flags. The Parallel Run is for data migrations: you run both old and new systems simultaneously, compare outputs, and only cut over when they match. In production, combine them. For example, when migrating a payment system, run the new service alongside the old one, sending a copy of traffic to both. Compare the results. If they diverge, fix the new service. Once confidence is high, shift traffic gradually. A common failure mode is not having a rollback plan. Always keep the old system running until the new one has been stable for at least a week. Also, monitor both systems during the parallel run. Use tools like CloudWatch Logs Insights or a custom dashboard to compare error rates and latency. The key is to automate the comparison—manual checking doesn't scale.

parallel_run_comparator.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import json
import hashlib

# Simulate comparing responses from old and new systems

def old_system(request):
    # Legacy implementation
    return {"status": "success", "transaction_id": "old-123"}

def new_system(request):
    # New implementation
    return {"status": "success", "transaction_id": "new-123"}

def compare_responses(old_resp, new_resp):
    # Compare relevant fields (exclude transaction_id if different)
    old_hash = hashlib.md5(json.dumps(old_resp, sort_keys=True).encode()).hexdigest()
    new_hash = hashlib.md5(json.dumps(new_resp, sort_keys=True).encode()).hexdigest()
    return old_hash == new_hash

# Simulate a request
request = {"amount": 100, "currency": "USD"}
old_resp = old_system(request)
new_resp = new_system(request)

if compare_responses(old_resp, new_resp):
    print("Responses match")
else:
    print(f"Mismatch: old={old_resp}, new={new_resp}")
Output
Mismatch: old={'status': 'success', 'transaction_id': 'old-123'}, new={'status': 'success', 'transaction_id': 'new-123'}
🔥Automate Comparison
Manual comparison doesn't scale. Use hash-based checks or field-level comparison. For high-volume systems, sample a percentage of requests and compare statistically.
📊 Production Insight
A payment processor migrated to a new system using parallel run. They compared 100% of transactions for 2 weeks. On day 10, they found a rounding error that caused a $0.01 discrepancy on 0.5% of transactions. Fixing it before cutover saved them from a potential audit nightmare.
🎯 Key Takeaway
Use Strangler Fig for incremental replacement and Parallel Run for data validation—always have a rollback plan.

Automation and Testing: The Non-Negotiables

No migration should be manual. Automate everything: infrastructure provisioning (Terraform, CloudFormation), configuration management (Ansible, Chef), and deployment (CI/CD pipelines). For testing, you need three layers: unit tests for code changes, integration tests for service interactions, and smoke tests for the migrated environment. A common failure mode is skipping smoke tests. After migration, run a suite of critical user journeys (login, search, checkout) to verify the system works. Also, test failure scenarios: what happens when an AZ goes down? Does your Multi-AZ setup actually fail over? Use AWS Fault Injection Simulator to test resilience. Another non-negotiable is rollback automation. Your migration script should have a --rollback flag that reverts all changes. Test the rollback in a staging environment before the real migration. Finally, monitor everything. Set up CloudWatch alarms for error rates, latency, and resource utilization. If something looks wrong, abort the migration. Better to delay than to break production.

.github/workflows/migration-smoke-test.ymlYAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
name: Migration Smoke Tests

on:
  workflow_dispatch:
    inputs:
      environment:
        description: 'Target environment'
        required: true
        default: 'staging'

jobs:
  smoke-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Run smoke tests
        run: |
          # Test critical endpoints
          curl -f https://${{ inputs.environment }}.example.com/health || exit 1
          curl -f -X POST https://${{ inputs.environment }}.example.com/api/login \
            -H "Content-Type: application/json" \
            -d '{"username":"test","password":"test"}' || exit 1
          echo "Smoke tests passed"
      - name: Notify on failure
        if: failure()
        run: |
          echo "Smoke tests failed! Aborting migration."
Output
Run smoke tests
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 14 100 14 0 0 7000 0 --:--:-- --:--:-- --:--:-- 7000
Smoke tests passed
⚠ Test Rollback Before Migration
I've seen teams skip rollback testing because 'it's just a config change.' Then the migration fails, and the rollback script has a bug. Test rollback in staging. Always.
📊 Production Insight
A retail company automated their entire migration pipeline with Terraform and Jenkins. During the cutover, a network ACL misconfiguration blocked traffic. The rollback script ran in 2 minutes, and they fixed the issue. Without automation, the outage would have lasted hours.
🎯 Key Takeaway
Automate everything, test thoroughly, and always have a tested rollback plan.

Post-Migration: The First 30 Days

The migration isn't over when traffic is flowing. The first 30 days are critical for stabilization and optimization. Your checklist: (1) Monitor cost—set up budgets and alerts. Many teams see a spike because of data transfer costs or over-provisioned resources. (2) Performance—compare latency and throughput to on-prem baselines. If something is slower, investigate. (3) Security—run a vulnerability scan and review IAM policies. (4) Backup—verify that backups are running and restorable. (5) Documentation—update runbooks, architecture diagrams, and incident response plans. A common failure mode is assuming the cloud is 'self-healing.' It's not. You need to configure auto-scaling, health checks, and recovery procedures. Also, schedule a post-mortem 2 weeks after migration. What went well? What could be improved? Document lessons learned for the next migration. Finally, celebrate. Migrations are hard. Acknowledge the team's effort.

post_migration_checklist.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/bin/bash
# Post-migration checklist automation

echo "=== Post-Migration Checklist ==="

# 1. Cost check
aws budgets describe-budgets --account-id $AWS_ACCOUNT_ID --query 'Budgets[0].BudgetLimit.Amount' --output text

# 2. Performance check (example: API latency)
aws cloudwatch get-metric-statistics \
  --namespace AWS/ApiGateway \
  --metric-name Latency \
  --dimensions Name=ApiName,Value=my-api \
  --start-time $(date -u -d '1 hour ago' '+%Y-%m-%dT%H:%M:%SZ') \
  --end-time $(date -u '+%Y-%m-%dT%H:%M:%SZ') \
  --period 300 \
  --statistics Average \
  --output json | jq '.Datapoints | .[0].Average'

# 3. Security scan (using AWS Inspector)
aws inspector2 list-findings --query 'findings[?severity==`HIGH`]' --output json | jq 'length'

# 4. Backup verification
aws backup list-recovery-points-by-backup-vault --backup-vault-name Default --query 'RecoveryPoints[0].Status' --output text

echo "Checklist complete. Review any issues."
Output
=== Post-Migration Checklist ===
5000
0.045
0
COMPLETED
Checklist complete. Review any issues.
💡Set Up Cost Alerts Immediately
Create a budget alert for 80% of your projected monthly spend. Cloud costs can spiral quickly due to data transfer or misconfigured resources. Catch it early.
📊 Production Insight
A startup migrated to AWS and saw their bill jump 3x in the first week. They had left a development RDS instance running with 10TB of storage. A simple budget alert would have caught it. They now have alerts at 50%, 80%, and 100% of budget.
🎯 Key Takeaway
The first 30 days are for stabilization—monitor cost, performance, security, and backups relentlessly.
Rehost vs Replatform: Key Trade-offs Comparing lift-and-shift with cloud optimization Rehost Replatform Migration Speed Fast (weeks) Moderate (months) Code Changes Minimal Some modifications Cloud Benefits Limited Moderate (auto-scaling, managed services Cost Optimization Often higher Improved efficiency Risk Level Low Medium THECODEFORGE.IO
thecodeforge.io
Aws Migration 7 Rs

Common Failure Modes and How to Avoid Them

Every migration has failure modes. Here are the top five I've seen: (1) 'Big Bang' cutover—trying to move everything at once. Always use incremental patterns. (2) Insufficient testing—skipping load testing or security scans. (3) Ignoring data migration complexity—data is always messier than you think. (4) Underestimating network latency—cloud is not on-prem. Test latency-sensitive applications early. (5) Lack of rollback plan—assume the migration will fail and plan for it. To avoid these, follow the principle of 'fail fast, fail small.' Migrate one workload at a time. Test each step. Have a rollback script ready. And communicate with stakeholders. A migration that surprises the business is a failed migration. Also, don't forget people. Your ops team needs training on cloud services. Your developers need to understand new deployment pipelines. Invest in training before the migration, not after.

migration_risk_checklist.pyPYTHON
1
2
3
4
5
6
7
8
9
10
risks = [
    {"name": "Big Bang cutover", "mitigation": "Use incremental migration (strangler fig)"},
    {"name": "Insufficient testing", "mitigation": "Automate smoke tests and load tests"},
    {"name": "Data migration complexity", "mitigation": "Run parallel systems and compare outputs"},
    {"name": "Network latency", "mitigation": "Test latency-sensitive apps early, consider Direct Connect"},
    {"name": "No rollback plan", "mitigation": "Automate rollback and test it in staging"},
]

for risk in risks:
    print(f"Risk: {risk['name']} -> Mitigation: {risk['mitigation']}")
Output
Risk: Big Bang cutover -> Mitigation: Use incremental migration (strangler fig)
Risk: Insufficient testing -> Mitigation: Automate smoke tests and load tests
Risk: Data migration complexity -> Mitigation: Run parallel systems and compare outputs
Risk: Network latency -> Mitigation: Test latency-sensitive apps early, consider Direct Connect
Risk: No rollback plan -> Mitigation: Automate rollback and test it in staging
⚠ Assume the Migration Will Fail
Plan for failure. Have a rollback script, a communication plan, and a war room. If you're not prepared for failure, you're not prepared for migration.
📊 Production Insight
A financial services firm attempted a big-bang migration of 200 applications over a weekend. They hit a DNS propagation issue that took 3 days to resolve. The business lost $5M in revenue. They now use incremental migrations with a 2-week parallel run for every workload.
🎯 Key Takeaway
Anticipate failure modes—big bang, insufficient testing, data complexity, latency, and lack of rollback—and mitigate them proactively.
⚙ Quick Reference
12 commands from this guide
FileCommand / CodePurpose
migration_decision_tree.pyworkload = {Why the 7 Rs Still Matter in 2025
rehost_rightsize.shINSTANCE_ID=$1Rehost
replatform_rds.tfresource "aws_db_instance" "migrated_db" {Replatform
strangler_fig.pydef monolith_login(username, password):Refactor
find_idle_servers.shaws ec2 describe-instances --query 'Reservations[*].Instances[*].[InstanceId,Tag...Retire
retain_decision.pyworkload = {Retain
eks-migration-pvc.yamlapiVersion: v1Relocate
saas_migration_etl.pylegacy_data = pd.DataFrame({Repurchase
parallel_run_comparator.pydef old_system(request):Real-World Migration Patterns
.githubworkflowsmigration-smoke-test.ymlname: Migration Smoke TestsAutomation and Testing
post_migration_checklist.shecho "=== Post-Migration Checklist ==="Post-Migration
migration_risk_checklist.pyrisks = [Common Failure Modes and How to Avoid Them

Key takeaways

1
Choose strategy per workload, not per organization
Each app has different business value, technical debt, and compliance needs. Applying the same R to everything is the #1 cause of migration failure.
2
Rehost is a trap for legacy apps with high licensing costs
Moving Oracle DB to EC2 doesn't reduce license fees. Always check if Replatform (e.g., Aurora) or Refactor (e.g., PostgreSQL) saves more.
3
Retain is a valid strategy, not a failure
Document why you retain an app, set a review cadence, and ensure it doesn't block other migrations. Retain without governance becomes technical debt.
4
Test rollback before migration day
Have a proven plan to revert to on-prem within hours. A failed migration without rollback can cause weeks of downtime. Practice it in a dry run.

Common mistakes to avoid

2 patterns
×

Overlooking aws migration 7 rs basic configuration

Symptom
Unexpected behavior in production
Fix
Follow AWS best practices and review documentation thoroughly
×

Ignoring cost implications

Symptom
Unexpected AWS bill at end of month
Fix
Set up billing alerts and use cost explorer to monitor usage
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
What is AWS Migration Strategies: The 7 Rs and Real-World Patterns and w...
Q02SENIOR
How do you secure AWS Migration Strategies: The 7 Rs and Real-World Patt...
Q03SENIOR
What are the cost optimization strategies for AWS Migration Strategies: ...
Q01 of 03JUNIOR

What is AWS Migration Strategies: The 7 Rs and Real-World Patterns and when would you use it?

ANSWER
AWS Migration Strategies: The 7 Rs and Real-World Patterns is an AWS service that helps manage cloud infrastructure efficiently. Use it when you need scalable, reliable cloud solutions.
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
What is the difference between Rehost and Replatform?
02
When should I choose Refactor over Rehost?
03
How do I handle databases during migration?
04
What is Retain and when is it appropriate?
05
How do I estimate migration costs for each R?
06
What's a common failure mode with the 7 Rs?
N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Notes here come from systems that actually shipped.

Follow
Verified
production tested
July 12, 2026
last updated
2,073
articles · all by Naren
🔥

That's AWS. Mark it forged?

8 min read · try the examples if you haven't

Previous
AWS Disaster Recovery: Backup, Pilot Light, and Multi-Region Strategies
41 / 54 · AWS
Next
Amazon ECR: Container Registry and Image Lifecycle