Migrate Azure VM Across Regions Using Azure CLI And Bash Script

Migrating an Azure Virtual Machine (VM) from one Azure Region to another can be necessary for several reasons, including disaster recovery, optimizing performance, or reducing latency. While Azure provides various options for replication and failover, one of the most efficient ways to migrate a VM manually involves creating a Snapshot of the VM Disks, copying those snapshots to the destination region, and recreating the VM in the new region.

This article walks you through the details of using the Azure CLI in a bash script for migrating a, Azure VM from one Azure Region to another. We will look at the script step-by-step, explain the purpose of each command, and guide you on how it works to help you migrate your VMs effectively.

Migration Script Overview

Th Azure CLI and bash commands in this article perform the following operations:

  1. Disallocate the source VM – This ensures that the source VM is safely shut down, and billing is stopped, before taking snapshots.
  2. Take a snapshot of the source VMs OS disk – This captures the current state of the source VMs OS disk, ensuring no data is lost.
  3. Copy the source snapshot to the destination region – This copies the source disk snapshot to the destination Azure Region.
  4. Create a disk from the copied snapshot in the destination region – The destinationdisk will be used to create the new VM in the new Azure Region.
  5. Create a new VM in the destination region – This recreates the VM from the copied disk in the destination Azure Region.
  6. Enable boot diagnostics on the destination VM – This allows for monitoring and troubleshooting the new VM.

Let’s dig into the actual Azure CLI and bash commands!

Setting Up the Azure Subscription

First, you need to set the context of the Azure CLI to the Azure Subscription you will be working with.

Use the following commands to do this:

az login
az account list
az account set --subscription "Build5Nines"

This logs into your Azure environment, lists the Azure Subscriptions you have access to, then sets the subscription context of the Azure CLI to the desired Azure Subscription. This ensures all subsequent Azure CLI commands run under the correct Azure Subscription. Replace Build5Nines with your Azure Subscription name or ID.

Defining Source and Destination Variables

Let’s define some Source and Destination variables for the VM that will be migrated. The example used for this article will be migrating a VM form the source resource group within the Azure East US region to the destination resource group within the Azure West US region. These details will be used when performing all the steps of the script, including stop the VM, take snapshots, and more.

Here, the script defines variables for the source resource group, VM name, and OS disk name in the source region.

SourceResourceGroupName="rg-b59-prd-eastus-app"
SourceVMName="vm-b59-prd-eastus-app"
SourceOSDisk="disk-b59-prd-eastus-app"

These variables define the destination resource group and details for the VM and OS disk in the new region.

DestinationResourceGroupName="rg-b59-prd-westus-app"
DestinationVMName="vm-b59-prd-westus-app"
DestinationOSDisk="disk-b59-prd-westus-app"
DestinationNicName="nic-b59-prd-westus-app"

The script in this article is assuming the Network Interface (Nic) resource for the destination VM already exists. If it doesn’t, then you’ll want to create it before migrating the VM.

Fetching Source VM Details

The script retrieves the locations of both the source and destination resource groups and stores these using variables within the bash script for reference later. These values are needed to ensure the migration happens between two different regions.

# Get Azure Region of the Source resource group
SourceLocation=$(az group show -n $SourceResourceGroupName --query location -o tsv)
# Set a name to use for the Source VM Disk Snapshot
SourceSnapshotName="Snapshot-$SourceOSDisk"
# Get Azure Region of the Destination resource group
DestinationLocation=$(az group show -n $DestinationResourceGroupName --query location -o tsv)
# Set a name to use for the Destination VM Disk Snapshot
DestinationSnapshotName="Snapshot-$DestinationVMName"

The next commands gather some critical information from the source VM, such as:

  • Computer name – Used for naming consistency in the new region.
  • VM size – Ensures that the new VM has the same resource allocation as the source VM.
  • OS type – Differentiates between windows and linux VMs.
  • Patch mode – Defines how the VM should handle system updates.
DestinationComputerName=$(az vm show --resource-group $SourceResourceGroupName --name $SourceVMName --query "osProfile.computerName" -o tsv)
DestinationVMSize=$(az vm show --resource-group $SourceResourceGroupName --name $SourceVMName --query "hardwareProfile.vmSize" -o tsv)
DestinationOSType=$(az vm show --resource-group $SourceResourceGroupName --name $SourceVMName --query "storageProfile.osDisk.osType" -o tsv)
DestinationVMPatchMode=$(az vm show --resource-group $SourceResourceGroupName --name $SourceVMName --query "osProfile.windowsConfiguration.patchSettings.patchMode" -o tsv)

The script command shown above are assuming the VM you are migrating is a windows VM. For a linux VM, you will likely want to use the following value for the patchMode instead:

DestinationVMPatchMode="ImageDefault"

Deallocating the Source VM

Now we’re ready to shutdown an deallocate the VM. Deallocating the VM safely shuts down the virtual machine, freeing up resources, stopping billing for the Virtual Machine resource, and preparing it for a disk snapshot.

az vm deallocate --resource-group $SourceResourceGroupName --name $SourceVMName

Deallocation is a crucial step to ensure the Azure disks are safely snapshotted. While you can snapshot a VM while it is running, it’s best to shutdown the VM prior to migration to ensure data loss is minimized.

Taking a Snapshot of the OS Disk

After identifying the disk attached to the source VM, the script creates a snapshot. A snapshot is a point-in-time backup of a disk, and this will allow us to create a copy of the VM disk in the destination region.

SourceOSDiskId=$(az disk show --resource-group $SourceResourceGroupName --name $SourceOSDisk --query id --output tsv)

az snapshot create 
    --resource-group $SourceResourceGroupName 
    --name $SourceSnapshotName 
    --source "$SourceOSDiskId" 
    --location $SourceLocation 
    --incremental true

Copying the Snapshot to the Destination Region

Here, the snapshot from the source region is copied to the destination region using an incremental approach. Incremental snapshots only copy changes made since the last snapshot, which makes the operation faster and more efficient. In this case, the snapshot is brand new and only contains a single version, which is the entire disks data.

SourceSnapshotId=$(az snapshot show --resource-group $SourceResourceGroupName --name $SourceSnapshotName --query "id" --output tsv)

az snapshot create 
    --resource-group $DestinationResourceGroupName 
    --name $DestinationSnapshotName 
    --source $SourceSnapshotId 
    --location $DestinationLocation 
    --incremental --copy-start 

Monitoring the Snapshot Copy Progress

This script can be used to checks the status of the snapshot in the destination region until it is fully provisioned. The script pauses for 30 seconds between checks to avoid overwhelming the Azure API.

snapshotStatus=$(az snapshot show --resource-group $DestinationResourceGroupName --name $DestinationSnapshotName --query "provisioningState" --output tsv)
while [ "$snapshotStatus" != "Succeeded" ]; do
    echo "$(date +'%Y-%m-%d %H:%M:%S') - Waiting for snapshot to be ready. Current status: $snapshotStatus"
    sleep 30
    snapshotStatus=$(az snapshot show --resource-group $DestinationResourceGroupName --name $DestinationSnapshotName --query "provisioningState" --output tsv)
done

If you navigate to the Snapshot within the Azure Portal, you can see the copy status there with an included percentage complete. It’s important to be aware that depending on the size of the Disk, the copy can take 15 minutes or more to complete.

Creating a Disk from the Snapshot

Once the snapshot is successfully copied, a new disk is created from it in the destination region. This disk will serve as the boot OS disk for the new VM.

DestinationSnapshotId=$(az snapshot show --resource-group $DestinationResourceGroupName --name $DestinationSnapshotName --query "id" --output tsv)

az disk create 
    --resource-group $DestinationResourceGroupName 
    --name $DestinationOSDisk 
    --source $DestinationSnapshotId 
    --location $DestinationLocation

Creating the VM in the Destination Region

This command creates a new VM in the destination region using the copied OS disk. The VM is configured with the same specifications (size, OS type, computer name, patch mode) as the source VM.

az vm create 
    --resource-group $DestinationResourceGroupName 
    --name $DestinationVMName 
    --attach-os-disk $DestinationOSDisk 
    --os-type ${DestinationOSType,,} 
    --size $DestinationVMSize 
    --nics $DestinationNicName 
    --patch-mode $DestinationVMPatchMode 
    --computer-name $DestinationComputerName 
    --nic-delete-option "Detach" 
    --os-disk-delete-option "Detach"

The scripts in this article are assuming the Network Interface (Nic) resource has already been created, and doesn’t include a command to create it. If you don’t have a Nic for your destination VM yet, you’ll want to create it before creating the VM resource, as that is a requirement for the resource.

Notice the --nic-delete-option and --os-disk-delete-option arguments are both being set to Detach; regardless of what the source VM is set to. I personally prefer to use Detach so that the Nic and Disk are not deleted if the VM resource is deleted. This allows you to be explicit in which specific Azure resources are deleted when the time comes. If you want, you can use the value of Delete for both or either of these options, then when you delete the VM resource the Nic and/or Disk will be automatically deleted too.

Enabling Boot Diagnostics

Finally, when creating a new VM using the Azure CLI, the boot diagnostics aren’t turned on by default. You can use the following command to enable boot diagnostics on the VM. This can be used to assist in troubleshooting any startup issues in the newly created VM, and is generally best practice to have turned on.

az vm boot-diagnostics enable 
    --resource-group $DestinationResourceGroupName 
    --name $DestinationVMName

Clean up Source VM Resources

Once you have verified the destination VM is working as expected, you can go ahead and delete the dource VM, source Snapshot and destination Snapshot. Generally, I wouldn’t recommend doing this for at least a couple weeks to ensure there are no issues with the destination VM to be safe. Although be sure to circle back and clean up the source VM and snapshots so you don’t have orphaned or abandoned Azure resources you’re paying for. The source VM disk and snapshots do incur storage costs.

Conclusion

This bash script efficiently migrates an Azure VM from one region to another by leveraging snapshots and disk replication. Each step—disallocating the VM, creating snapshots, copying them across regions, and re-creating the VM—ensures a smooth transition without data loss.

The key advantages of this approach are:

  • Minimal downtime: The VM is only down during the deallocation and snapshot phases.
  • Efficient resource utilization: By using incremental snapshots and copying only the necessary data, the script minimizes costs and time.
  • Automation: Once the script is set up and run, the entire migration process is automated, reducing the potential for human error.

Feel free to modify the script based on your specific use case, such as changing regions or resource group names, but the core steps will remain the same.

Happy migrating!