Step-by-step guide to migrating workloads to Azure, AWS, or GCP. Covers assessment, dependency mapping, migration strategies (6 Rs), and cutover planning.
This playbook guides engineers through the technical decisions and execution steps of planning a cloud migration, from assessing existing systems to finalizing a migration strategy. It applies when a team must move on-premises or legacy systems to a public cloud, whether for scalability, cost efficiency, or operational agility. The process assumes no prior cloud experience and produces a concrete, executable plan grounded in real tools and workflows.
Begin by gathering a complete, actionable inventory of all systems in the current environment. Use a standardized set of queries and scripts to extract architecture, dependencies, and operational behavior.
Run a discovery script that collects application, database, and infrastructure metadata. The script should output a JSON document with the following keys:
{
"workloads": [
{
"name": "user-service",
"type": "web",
"host": "app01.prod.internal",
"ports": [8080, 8443],
"dependencies": [
"db01.prod.internal:5432",
"redis01.prod.internal:6379"
],
"processes": [
"java -jar user-service.jar",
"node index.js"
],
"disk_usage": 124.3,
"memory_usage": 3.7,
"cpu_usage": 48.2
}
],
"databases": [
{
"name": "user-db",
"host": "db01.prod.internal",
"port": 5432,
"type": "postgresql",
"size": 450,
"tables": 112,
"active_connections": 67,
"backup_schedule": "0 2 * * *"
}
]
}
Use ps aux and netstat -tuln to list running processes and open ports. For each workload, confirm its network footprint:
$ netstat -tuln | grep 8080
tcp6 0 0 :::8080 :::* LISTEN 12345/java
Map dependencies between services using tcpdump and strace to trace network and system calls during peak load.
$ sudo tcpdump -i any -s 0 -w /tmp/dependency-trace.pcap -nn host db01.prod.internal
$ sudo strace -o /tmp/service-call-trace.log -f -e trace=connect,send,recv,accept -p 12345
Inspect the resulting .pcap and .log files with tshark and grep:
$ tshark -r /tmp/dependency-trace.pcap -Y "tcp.dstport == 5432" -T fields -e frame.time_epoch -e tcp.srcport -e tcp.dstport
Build a dependency graph where each service is a node and network calls are edges. Services with high latency or frequent failures are likely candidates for re-architecting.
For each database or file server, extract schema, size, and access patterns.
$ psql -h db01.prod.internal -U app -d user_db -c "SELECT schemaname, tablename, row_count, total_size FROM pg_tables WHERE schemaname = 'public';"
Use du and lsof to identify large files and active files:
$ sudo du -sh /var/lib/postgresql/data/* | sort -hr | head -10
$ sudo lsof +D /var/lib/postgresql/data/ | grep -v 'CLOSED' | head -20
The output reveals which data volumes are hot, cold, or frequently modified — informing data tiering and migration strategy.
Translate high-level business drivers into technical requirements. Use a structured format to ensure clarity and traceability.
For each system, define measurable non-functional goals:
Use wrk to benchmark current systems under target load:
$ wrk -t12 -c400 -d30s --latency http://user-service.prod.internal/api/users
Observe the output:
Running 30s test @ http://user-service.prod.internal/api/users
12 threads and 400 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 142ms 123ms 8.41s 65.25%
Req/Sec 421.30 89.44 1.25k 60.24%
Latency Distribution
50% 120.00ms
75% 180.00ms
90% 250.00ms
99% 450.00ms
This data becomes the baseline for validating migration success.
Choose one of four patterns based on system characteristics and risk tolerance.
Move existing virtual machines or containers with minimal changes. Ideal for systems with complex local dependencies.
Use rsync and tar to copy disk images to the cloud:
$ rsync -avz --partial --compress-level=6 /var/lib/vm/ user@cloud-host:/home/user/vms/
$ tar -czf /tmp/user-service.tar.gz -C /opt/app user-service.jar config/
Deploy using terraform:
resource "aws_instance" "user_service" {
ami = "ami-12345678"
instance_type = "t3.medium"
key_name = "prod-key"
subnet_id = "subnet-98765432"
user_data = <<-EOT
#!/bin/bash
sudo apt update
sudo apt install -y openjdk-11-jre
sudo cp /home/user/vms/user-service.tar.gz /opt/app/
cd /opt/app
sudo tar -xzf user-service.tar.gz
sudo systemctl enable user-service.service
EOT
}
Refactor systems to leverage cloud-native features without full rearchitecture.
Use pg_dump and pg_restore to migrate PostgreSQL:
$ pg_dump -h db01.prod.internal -U app -d user_db -F c -Z 9 -f /tmp/user_db.dump
$ pg_restore -h rds-user-db.c12345678901.us-east-1.rds.amazonaws.com -U app -d user_db /tmp/user_db.dump
Update connection strings and test with psql:
$ psql -h rds-user-db.c12345678901.us-east-1.rds.amazonaws.com -U app -d user_db -c "SELECT version();"
Break monolithic systems into microservices using event-driven or API-based patterns.
Use docker-compose to define service boundaries:
version: '3.8'
services:
user-service:
build: ./user-service
ports:
- "8080:8080"
environment:
- DATABASE_URL=postgresql://app:secret@db:5432/user_db
depends_on:
- db
- event-bus
db:
image: postgres:14
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: secret
POSTGRES_DB: user_db
volumes:
- pgdata:/var/lib/postgresql/data
event-bus:
image: redis:7
ports:
- "6379:6379"
Use kafka to decouple services:
$ kafka-topics.sh --create --topic user-events --bootstrap-server kafka:9092 --partitions 4 --replication-factor 3
$ kafka-console-consumer.sh --bootstrap-server kafka:9092 --topic user-events --group user-consumer-group
Build new systems from scratch using cloud-native patterns and tools.
Use git and GitHub Actions to define CI/CD pipelines:
name: Deploy to Production
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build
run: |
docker build -t user-service:latest .
- name: Push to ECR
run: |
aws ecr create-repository --repository-name user-service
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin $ECR_REGISTRY
docker tag user-service:latest $ECR_REGISTRY/user-service:latest
docker push $ECR_REGISTRY/user-service:latest
- name: Deploy to ECS
run: |
aws ecs update-service --cluster user-cluster --service user-service --force-new-deployment
Create a clear, executable blueprint for the target environment.
Use VPC and security groups to isolate workloads:
{
"vpc": {
"cidr_block": "10.0.0.0/16",
"availability_zones": ["us-east-1a", "us-east-1b"],
"public_subnets": [
{ "cidr": "10.0.1.0/24", "az": "us-east-1a" },
{ "cidr": "10.0.2.0/24", "az": "us-east-1b" }
],
"private_subnets": [
{ "cidr": "10.0.3.0/24", "az": "us-east-1a" },
{ "cidr": "10.0.4.0/24", "az": "us-east-1b" }
]
},
"security_groups": {
"web": {
"description": "Allow HTTP and HTTPS",
"rules": [
{ "protocol": "tcp", "port": 80, "source": "0.0.0.0/0" },
{ "protocol": "tcp", "port": 443, "source": "0.0.0.0/0" }
]
},
"database": {
"description": "Allow app and backup access",
"rules": [
{ "protocol": "tcp", "port": 5432, "source": "10.0.1.0/24" },
{ "protocol": "tcp", "port": 5432, "source": "10.0.3.0/24" }
]
}
}
}
Use curl and iptables to validate network reachability:
$ curl -H "Host: user-service.prod.internal" http://10.0.1.10:8080/api/users
$ sudo iptables -L -n | grep 8080
For each service, define a data model and storage strategy:
Use aws s3api to create lifecycle rules:
$ aws s3api put-lifecycle-configuration \
--bucket user-data \
--lifecycle-configuration '{
"Rules": [
{
"ID": "archive-old-data",
"Filter": { "Prefix": "archive/" },
"Status": "Enabled",
"Transitions": [
{
"Days": 30,
"StorageClass": "STANDARD_IA"
},
{
"Days": 90,
"StorageClass": "GLACIER"
}
]
}
]
}'
Before migration execution, test the plan end-to-end.
Migrate a non-critical workload to the cloud and measure performance.
Use kubernetes to deploy the pilot:
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service-pilot
spec:
replicas: 3
selector:
matchLabels:
app: user-service
template:
metadata:
labels:
app: user-service
spec:
containers:
- name: user-service
image: user-service:latest
ports:
- containerPort: 8080
env:
- name: DATABASE_URL
value: "postgresql://app:[email protected]:5432/user_db"
Use kubectl to monitor rollout:
$ kubectl rollout status deployment/user-service-pilot
$ kubectl get pods -l app=user-service --watch
After migration, validate that systems meet non-functional requirements using the same tools as in assessment.
Check latency with wrk:
$ wrk -t12 -c400 -d30s --latency http://user-service.prod.internal/api/users
Verify availability with curl and systemctl:
$ curl -s http://user-service.prod.internal/health | jq .
{
"status": "UP",
"db": {
"status": "UP",
"responseTime": 120
},
"redis": {
"status": "UP",
"responseTime": 45
}
}
Check logs with journalctl and grep:
$ journalctl -u user-service.service --since "2024-03-15" | grep "ERROR" | tail -20
Convert the plan into a living document. Include:
pg_waldump”).Use git to version the playbook:
$ git add .
$ git commit -m "Add migration plan for user-service"
$ git push origin main
Ensure every step is executable by any engineer, regardless of prior cloud experience.
This page was rewritten on 17 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