How to Plan a Cloud Migration: Complete Technical Playbook

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.

Assess the Current State

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.

Identify Workloads and Components

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

Analyze Dependencies and Data Flow

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.

Evaluate Data and Storage

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.

Define Migration Goals and Constraints

Translate high-level business drivers into technical requirements. Use a structured format to ensure clarity and traceability.

Set Non-Functional Requirements

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.

Select a Migration Strategy

Choose one of four patterns based on system characteristics and risk tolerance.

Rehost (Lift and Shift)

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
}

Replatform (Lift, Tinker, and Optimize)

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();"

Refactor (Re-architect)

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

Rebuild (Start Fresh)

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

Design the Cloud Architecture

Create a clear, executable blueprint for the target environment.

Define Networking and Security

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

Choose Data and Storage Patterns

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"
            }
          ]
        }
      ]
    }'

Validate and Finalize the Plan

Before migration execution, test the plan end-to-end.

Run a Pilot Migration

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

Verify Migration Success

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

Document the Migration Playbook

Convert the plan into a living document. Include:

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.