Plan and execute cloud migrations. Covers the 7R strategies (rehost, replatform, refactor, repurchase, retire, retain, relocate), migration waves, dependency mapping, and cutover planning.
Cloud migration is the process of moving workloads, data, and applications from on-premises infrastructure to cloud platforms. The 7R Framework is a decision model used to evaluate and select the most appropriate migration strategy for each workload. It answers the question: *Should we rehost, refactor, replatform, rebuild, replace, retain, or retire this system?* This framework is most valuable during the planning and execution phases of migration, when teams must choose between competing approaches for large portfolios of applications, especially when balancing time, cost, risk, and technical debt.
Start by cataloging existing systems: services, databases, batch jobs, monoliths, and legacy applications. For each, determine its current state: deployment model, technology stack, ownership, performance, and operational complexity.
Use standard tools to extract metadata:
psql -c "SELECT name, tech_stack, owner, uptime, avg_response_time FROM services WHERE status = 'active'"
Then assess each system’s migration readiness using a set of criteria:
The 7R Framework is applied when a workload’s migration readiness score exceeds a threshold, typically after a discovery phase using tools like nmap, top, sar, docker stats, and systemctl status.
Each workload is mapped to one of the seven R’s, defined as:
Move an existing system to the cloud with minimal changes. Ideal for legacy applications with low technical debt and high business value.
Use docker build and docker push to package the application:
docker build -t myapp:v1.2.0 .
docker push myapp:v1.2.0
Then deploy to a virtual machine using ssh and systemctl:
ssh user@ec2-instance 'sudo systemctl stop myapp'
ssh user@ec2-instance 'sudo systemctl start myapp'
The key trade-off is minimal effort vs. suboptimal performance. Common failure modes:
myapp.conf file is not versioned or managed centrally.systemctl unit fails due to missing dependencies, but journalctl output shows Failed to start myapp.service.To detect this, run:
journalctl -u myapp.service --since "2024-04-01" --no-pager
The most common silent failure: the application starts, but health checks fail because the health endpoint is not exposed on port 8080, even though it is documented.
Enhance the platform with minimal code changes. Common improvements include:
Use aws ec2 run-instances with a JSON config:
{
"ImageId": "ami-12345678",
"InstanceType": "t3.small",
"MinCount": 2,
"MaxCount": 10,
"KeyName": "prod-key",
"SecurityGroups": ["web-sg", "db-sg"],
"TagSpecifications": [
{
"ResourceType": "instance",
"Tags": [
{ "Key": "Environment", "Value": "production" },
{ "Key": "Application", "Value": "myapp" }
]
}
]
}
Apply this via aws ec2 run-instances --cli-input-json file://instance-config.json.
A common error: the auto-scaling group scales out, but the new instances fail health checks because the health endpoint is not available until the database is ready. The health probe returns 503 Service Unavailable, but the logs show the application started before the DB connection pool initialized.
Fix by adding a db-ready script to the instance startup:
#!/bin/sh
until pg_isready -h db-host -p 5432 -U app_user; do
echo "Waiting for PostgreSQL..."
sleep 2
done
exec java -jar myapp.jar
This script is often omitted, leading to inconsistent availability during scaling events.
Refactor an application to take advantage of cloud-native features—stateless services, event-driven architecture, and decoupled components.
Break a monolith into services using docker-compose and kubernetes:
# docker-compose.yml
version: '3.8'
services:
user-service:
build: ./user-service
ports:
- "8080:8080"
depends_on:
- postgres
environment:
- DATABASE_URL=postgres://user:pass@postgres:5432/users
order-service:
build: ./order-service
ports:
- "8081:8081"
depends_on:
- postgres
environment:
- DATABASE_URL=postgres://user:pass@postgres:5432/orders
postgres:
image: postgres:15
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: orders
Deploy with docker-compose up -d.
The key challenge: data consistency across services. A transaction spans user-service and order-service, but one fails while the other commits.
Use a message queue (e.g., RabbitMQ or Kafka) to decouple operations:
# Publish an event on startup
curl -X POST http://event-bus:5000/events \
-H "Content-Type: application/json" \
-d '{"event": "user.created", "data": {"id": 123, "name": "Alice"}}'
The most common failure: event delivery duplicates. A message is processed twice because the consumer commits offsets before processing the full event. The solution: use transactional consumers and idempotent processing.
Rewrite the application from scratch for the cloud. This is best for high-value, high-impact systems with significant technical debt.
Use kubectl to define a deployment and service:
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
spec:
replicas: 3
selector:
matchLabels:
app: user-service
template:
metadata:
labels:
app: user-service
spec:
containers:
- name: user-service
image: user-service:v2.0.0
ports:
- containerPort: 8080
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: db-creds
key: url
Apply with kubectl apply -f deployment.yaml.
The silent failure: pod startup latency. The service responds slowly during the first 30 seconds after deployment because the imagePullPolicy is set to IfNotPresent, but the image is not cached on the nodes.
Fix by setting imagePullPolicy: Always and using initContainers to warm up the database:
initContainers:
- name: db-warmup
image: busybox
command: ['sh', '-c', 'sleep 5 && echo "Warming up database..."']
volumeMounts:
- name: data
mountPath: /data
Swap an existing system with a cloud-based service. This is common for email, identity, analytics, and monitoring.
Migrate from an in-house customer support system to a SaaS platform like Zendesk or Salesforce.
Use curl to import data:
curl -X POST https://api.zendesk.com/v2/imports \
-H "Authorization: Bearer $API_TOKEN" \
-H "Content-Type: application/json" \
-d @customer-data.json
The customer-data.json file must have a specific schema:
{
"import": {
"type": "users",
"data": [
{
"email": "[email protected]",
"name": "Alice Johnson",
"external_id": "12345"
}
]
}
}
Common failure: data consistency between old and new systems. A new customer is created in the SaaS system, but the old system is not updated. The fix: use webhooks to sync changes.
Set up a POST /webhook endpoint that triggers on user.created:
curl -X POST https://myapp.com/webhook \
-H "Content-Type: application/json" \
-d '{"event": "user.created", "user_id": 123}'
The most frequent error: missing or malformed webhooks. The SaaS system sends user.created events, but the endpoint is not reachable due to firewall rules or missing SSL certificates. The error response includes a 400 Bad Request with {"error": "Invalid payload format"}.
Decide to keep a system in its current environment, often due to high cost of migration or tight integration with on-premises systems.
Use systemctl status and journalctl to monitor the service:
systemctl status legacy-app
journalctl -u legacy-app --since "1h" --unit=legacy-app
The key challenge: interoperability with cloud systems. The retained system must communicate with cloud services via APIs, message queues, or file transfers.
Use scp and rsync to transfer data:
rsync -avz --delete /data/ user@cloud-host:/backup/
A common failure: data transfer corruption. Files are transferred, but checksums do not match. The fix: use md5sum and diff to verify integrity:
md5sum /data/*.csv > checksums.txt
scp checksums.txt user@cloud-host:/backup/
ssh user@cloud-host 'md5sum -c checksums.txt'
The silent failure: time drift between on-premises and cloud systems. Logs show events at different times, but date on the VM shows a different time than the cloud instance. The fix: configure chrony or ntpd on all systems.
Completely remove a system from operation.
Use systemctl stop and systemctl disable to shut down services:
systemctl stop legacy-app
systemctl disable legacy-app
Then archive the data:
tar -czf legacy-app-backup.tar.gz /var/lib/legacy-app /etc/legacy-app
The most common failure: orphaned data. The application is gone, but its data remains in an unstructured format.
Use find and du to locate and analyze data:
find /var/lib/legacy-app -type f -name "*.log" -mtime +30 -exec du -h {} \;
The silent failure: missing backup verification. The backup is created, but no one checks if it can be restored.
Use docker run to verify the backup:
docker run --rm -v $(pwd):/backup alpine sh -c "tar -xzf /backup/legacy-app-backup.tar.gz -C /restore && ls /restore"
The framework is applied iteratively. Start by categorizing workloads, then refine the strategy based on feedback.
Use a spreadsheet with columns:
Update this spreadsheet after each migration phase. Use git to track changes:
git add .
git commit -m "Updated 7R framework for Q2 migration"
git push origin main
The most valuable insight: no single R fits all. A system may start as a Rehost, then evolve into Refactor, then Rebuild. The 7R Framework is not a one-time decision but a living document guiding the cloud journey.
This page was rewritten on 16 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy