Theoretical Foundation: Move a Virtual Machine to Another Resource Group, Subscription, or Region
1. Initial Intuitionβ
Imagine you live in an apartment and decide to move. Depending on the destination, the move has very different implications:
- Moving to another apartment in the same building (another Resource Group, same subscription): you take everything with you, the address changes, but you remain in the same condominium with the same rules.
- Moving to another building in the same neighborhood (another Subscription, same tenant): you take everything with you, but now you're in another condominium with different rules, another manager, another gas bill.
- Moving to another city (another region): you can't carry the heavy furniture. You need to acquire everything new at the destination or transport piece by piece with significant effort.
Moving a VM in Azure follows exactly this logic. Moving between Resource Groups or Subscriptions is a metadata operation that preserves the resource intact. Moving between regions is a physical migration operation that requires a specialized tool and involves actual creation of new resources.
2. Contextβ
Why moving resources exists as an operationβ
Organizations evolve. A VM created in a temporary project may need to be transferred to the application's permanent Resource Group. A startup acquired by the company brings VMs in separate subscriptions that need to be consolidated. An expansion to new markets requires moving infrastructure to regions closer to users.
The operation of moving VMs exists to accommodate these needs without having to delete and recreate infrastructure, which would cause data loss, downtime and complete reconfiguration.
What depends on moving VMs correctlyβ
- RBAC: existing role assignments in the source RG or Subscription are not transferred
- Azure Policy: the destination may have different policies that make the VM non-compliant
- Networking: dependencies on VNet, NSG and public IP need to be managed
- Backup: Azure Backup jobs in the source RG need to be reconfigured at the destination
- Monitoring: alerts and Diagnostic Settings point to the old Resource ID
- Cost Management: budgets and tags need to be reviewed at the destination
3. Building Conceptsβ
3.1 The three types of moves and their naturesβ
3.2 What changes in the Resource ID when movingβ
The Resource ID of a VM includes the complete path in the ARM hierarchy:
/subscriptions/{subId}/resourceGroups/{rgName}/providers/Microsoft.Compute/virtualMachines/{vmName}
When moving to another RG (same Sub):
FROM: /subscriptions/AAA/resourceGroups/rg-origem/providers/Microsoft.Compute/virtualMachines/vm-01
TO: /subscriptions/AAA/resourceGroups/rg-destino/providers/Microsoft.Compute/virtualMachines/vm-01
Only the resourceGroups changes.
When moving to another Subscription:
FROM: /subscriptions/AAA/resourceGroups/rg-origem/providers/Microsoft.Compute/virtualMachines/vm-01
TO: /subscriptions/BBB/resourceGroups/rg-destino/providers/Microsoft.Compute/virtualMachines/vm-01
Both subscriptions and resourceGroups change.
Implication: any hardcoded reference to the old Resource ID in scripts, alerts, RBAC, policies or applications breaks after the move.
3.3 Resources that accompany the VM in a RG/Subscription moveβ
A VM rarely exists alone. It has dependencies that need to be moved together or explicitly handled:
General rule: the VM, its NIC and its managed disks must be moved together. VNets and NSGs can remain in the network RG (cross-RG networking works normally).
3.4 Restrictions when moving VMsβ
There are situations that prevent the move:
| Condition | Can move? | Reason |
|---|---|---|
| VM with active Azure Disk Encryption (ADE) | No (to another Sub) | Key Vault must be in same Sub; move KV together or decrypt first |
| VM in Availability Set being moved without the AS | No | AS must be moved together with all VMs |
| VM with Proximity Placement Group | Conditional | PPG must be moved together if it exists |
| VM with backup Extension configured | Yes, but with attention | Backup jobs need to be reconfigured at destination |
| VM with Ultra SSD or Premium SSD v2 disks | Check | Some restrictions by disk type |
| Running VM (for cross-subscription move) | Yes | Metadata move doesn't require stopping the VM |
| VM with Classic Administrators | Not recommended | Legacy model; migrate to ARM first |
3.5 Azure Resource Mover: for movement between regionsβ
To move a VM to another region, the official tool is Azure Resource Mover. It:
- Creates new resources at the destination based on source configuration
- Replicates disk data to the destination region
- Allows validation and testing at destination before committing
- Discards source resources after commit
This is the only supported way to "move" a VM between regions. Technically it's not a move but a migration: new resources are created and the old ones are deleted.
Alternatives to Resource Mover for region migration:
- Azure Site Recovery (for VMs with critical workloads that need continuous replication)
- Capture image + deploy in new region + delete original
- Manually export/import VHD (not recommended for production)
4. Structural Viewβ
Complete flow: move VM between Resource Groupsβ
Migration flow between regions with Azure Resource Moverβ
5. Practical Operationβ
Non-obvious and critical behaviorsβ
The move operation applies a temporary lock on resources. During the move (which can take from seconds to minutes for many resources), Azure applies a lock on the source resources. No operation can be performed on them until the move finishes. If the move fails, the resources are released in their original state.
Move between subscriptions requires both to belong to the same tenant. It's not possible to move VMs between subscriptions from different tenants via native move. For this it's necessary to recreate at the destination or use Azure Site Recovery.
RBAC is never transferred automatically. This is the most impactful change in a cross-subscription move: all role assignments in the source RG or subscription are lost. The VM receiver needs to receive new access at the destination. In an environment with many users and permissions, this can be significant work.
Resource providers must be registered in the destination subscription.
If you move a VM to a subscription that never had VMs, the resource providers Microsoft.Compute, Microsoft.Network and Microsoft.Storage need to be registered. Otherwise, the move fails with a resource provider not registered error.
Managed Disks move together with the VM but may have zone restrictions. If the VM uses Availability Zones and is in Zone 1 in brazilsouth, the managed disks are also tied to that zone. When moving to another RG, the zone is preserved. But when moving to another subscription, verify if the zone is available at the destination.
Diagnostic Settings and alerts become orphaned. After a move, the Diagnostic Settings configured on the VM or disks continue to exist, but point to the old Resource ID that no longer exists. You'll see logs being sent to Log Analytics with incorrect references. Reconfiguration is necessary.
Azure Backup doesn't follow the VM. If the VM was protected by Azure Backup in a Recovery Services Vault in the source RG, after the move the VM continues registered in the vault but the association can become inconsistent. Recommendation: stop backup protection before the move and reconfigure at the destination.
6. Implementation Methodsβ
Azure Portalβ
When to use: one-off moves, one VM at a time, visual verification of dependencies
To move to another RG or Subscription:
- Portal > Resource Groups > select source RG
- Select the resources to be moved (VM + NIC + Disks + Public IP, if applicable)
- Click Move > Move to another resource group or Move to another subscription
- The portal automatically validates dependencies and displays warnings
- Select the destination RG (or destination subscription + RG)
- Confirm that you understand that Resource IDs will change
- Move
To move between regions (via Resource Mover):
- Portal > search Azure Resource Mover
- Move > Move to another region
- Select subscription, source RG
- Add VM and let Resource Mover identify dependencies
- Follow the preparation, validation and commit wizard
Limitation: for large volumes, the portal is slow and impractical.
Azure CLIβ
# Validate if resources support move before executing
az resource invoke-action \
--action "validateMoveResources" \
--ids "/subscriptions/<sub-id>/resourceGroups/rg-origem" \
--request-body "{
\"resources\": [
\"/subscriptions/<sub-id>/resourceGroups/rg-origem/providers/Microsoft.Compute/virtualMachines/vm-01\",
\"/subscriptions/<sub-id>/resourceGroups/rg-origem/providers/Microsoft.Network/networkInterfaces/nic-vm-01\",
\"/subscriptions/<sub-id>/resourceGroups/rg-origem/providers/Microsoft.Compute/disks/vm-01-osdisk\"
],
\"targetResourceGroup\": \"/subscriptions/<sub-id>/resourceGroups/rg-destino\"
}"
# Move VM to another Resource Group (same Subscription)
# Get IDs of all resources to move
VM_ID=$(az vm show --resource-group rg-origem --name vm-01 --query id --output tsv)
NIC_ID=$(az vm show --resource-group rg-origem --name vm-01 --query "networkProfile.networkInterfaces[0].id" --output tsv)
OS_DISK_ID=$(az vm show --resource-group rg-origem --name vm-01 --query "storageProfile.osDisk.managedDisk.id" --output tsv)
DATA_DISK_IDS=$(az vm show --resource-group rg-origem --name vm-01 --query "storageProfile.dataDisks[].managedDisk.id" --output tsv)
# Execute the move
az resource move \
--destination-group "rg-destino" \
--ids "$VM_ID" "$NIC_ID" "$OS_DISK_ID" $DATA_DISK_IDS
# Move to another Subscription
az resource move \
--destination-group "rg-destino" \
--destination-subscription-id "<sub-destino-id>" \
--ids "$VM_ID" "$NIC_ID" "$OS_DISK_ID"
# Check resource providers in destination subscription (for cross-sub move)
az provider show \
--namespace "Microsoft.Compute" \
--subscription "<sub-destino-id>" \
--query "registrationState" \
--output tsv
# Register providers if needed
az provider register \
--namespace "Microsoft.Compute" \
--subscription "<sub-destino-id>"
az provider register \
--namespace "Microsoft.Network" \
--subscription "<sub-destino-id>"
az provider register \
--namespace "Microsoft.Storage" \
--subscription "<sub-destino-id>"
# After move: verify VM is in new RG
az vm show \
--resource-group "rg-destino" \
--name "vm-01" \
--query "{Name: name, RG: resourceGroup, Location: location}" \
--output json
# Complete script: move VM with all associated resources
RESOURCE_GROUP_ORIGEM="rg-origem"
RESOURCE_GROUP_DESTINO="rg-destino"
VM_NAME="vm-01"
SUB_ID=$(az account show --query id --output tsv)
echo "Collecting resource IDs associated with $VM_NAME..."
VM_ID="/subscriptions/$SUB_ID/resourceGroups/$RESOURCE_GROUP_ORIGEM/providers/Microsoft.Compute/virtualMachines/$VM_NAME"
# Collect NICs
NIC_IDS=$(az vm show \
--resource-group "$RESOURCE_GROUP_ORIGEM" \
--name "$VM_NAME" \
--query "networkProfile.networkInterfaces[].id" \
--output tsv)
# Collect OS disk
OS_DISK_ID=$(az vm show \
--resource-group "$RESOURCE_GROUP_ORIGEM" \
--name "$VM_NAME" \
--query "storageProfile.osDisk.managedDisk.id" \
--output tsv)
# Collect data disks
DATA_DISK_IDS=$(az vm show \
--resource-group "$RESOURCE_GROUP_ORIGEM" \
--name "$VM_NAME" \
--query "storageProfile.dataDisks[].managedDisk.id" \
--output tsv)
# Collect public IP (if any)
PUBLIC_IP_IDS=$(for nic_id in $NIC_IDS; do
az network nic show --ids "$nic_id" \
--query "ipConfigurations[].publicIpAddress.id" \
--output tsv 2>/dev/null
done)
echo "Executing move..."
az resource move \
--destination-group "$RESOURCE_GROUP_DESTINO" \
--ids "$VM_ID" $NIC_IDS "$OS_DISK_ID" $DATA_DISK_IDS $PUBLIC_IP_IDS
echo "Move completed. Verifying..."
az vm show \
--resource-group "$RESOURCE_GROUP_DESTINO" \
--name "$VM_NAME" \
--query "{Name: name, RG: resourceGroup}" \
--output json
Azure PowerShellβ
# Move VM to another Resource Group
$vm = Get-AzVM -ResourceGroupName "rg-origem" -Name "vm-01"
# Collect IDs of associated resources
$nicIds = $vm.NetworkProfile.NetworkInterfaces.Id
$osDiskId = $vm.StorageProfile.OsDisk.ManagedDisk.Id
$dataDiskIds = $vm.StorageProfile.DataDisks.ManagedDisk.Id
# Collect public IPs from NICs
$publicIpIds = @()
foreach ($nicId in $nicIds) {
$nic = Get-AzNetworkInterface -ResourceId $nicId
$publicIpIds += $nic.IpConfigurations.PublicIpAddress.Id | Where-Object { $_ -ne $null }
}
# Combine all IDs
$allIds = @($vm.Id) + $nicIds + $osDiskId + $dataDiskIds + $publicIpIds
$allIds = $allIds | Where-Object { $_ -ne $null }
# Execute move
Move-AzResource `
-DestinationResourceGroupName "rg-destino" `
-ResourceId $allIds
# Move to another Subscription
Move-AzResource `
-DestinationResourceGroupName "rg-destino" `
-DestinationSubscriptionId "<sub-destino-id>" `
-ResourceId $allIds
# Verify result
Get-AzVM -ResourceGroupName "rg-destino" -Name "vm-01" |
Select-Object Name, ResourceGroupName, Location
Azure Resource Mover via CLI (migration between regions)β
# Create Move Collection in Resource Mover
az resource-mover move-collection create \
--name "move-collection-brazilsouth-to-eastus" \
--resource-group "rg-resource-mover" \
--source-region "brazilsouth" \
--target-region "eastus" \
--identity-type SystemAssigned
# Add VM to move collection
az resource-mover move-resource add \
--move-collection-name "move-collection-brazilsouth-to-eastus" \
--resource-group "rg-resource-mover" \
--source-id "/subscriptions/<sub-id>/resourceGroups/rg-origem/providers/Microsoft.Compute/virtualMachines/vm-01"
# Resolve dependencies automatically
az resource-mover move-collection resolve-dependency \
--move-collection-name "move-collection-brazilsouth-to-eastus" \
--resource-group "rg-resource-mover"
# Prepare resources (starts data replication)
az resource-mover move-resource initiate-move \
--move-collection-name "move-collection-brazilsouth-to-eastus" \
--resource-group "rg-resource-mover" \
--move-resources "/subscriptions/<sub-id>/resourceGroups/rg-resource-mover/providers/Microsoft.Migrate/moveCollections/move-collection-brazilsouth-to-eastus/moveResources/vm-01"
# Check preparation status
az resource-mover move-resource list \
--move-collection-name "move-collection-brazilsouth-to-eastus" \
--resource-group "rg-resource-mover" \
--query "[].{Name: name, State: moveStatus.moveState}" \
--output table
# Commit the move (after validating at destination)
az resource-mover move-collection commit \
--move-collection-name "move-collection-brazilsouth-to-eastus" \
--resource-group "rg-resource-mover" \
--move-resources "/subscriptions/<sub-id>/..."
# Delete source resources (after commit)
az resource-mover move-collection discard \
--move-collection-name "move-collection-brazilsouth-to-eastus" \
--resource-group "rg-resource-mover" \
--move-resources "/subscriptions/<sub-id>/..."
7. Control and Securityβ
Required permissions to execute a moveβ
| Operation | Required permission |
|---|---|
| Move to another RG (same Sub) | Microsoft.Resources/subscriptions/resourceGroups/moveResources/action on source RG + Contributor on destination RG |
| Move to another Subscription | Owner or Contributor on both subscriptions |
| Use Azure Resource Mover | Contributor on both source and destination subscriptions |
The simplest way to ensure correct permissions is to have Contributor on both the source and destination RG/Subscription.
What to do with RBAC after cross-subscription moveβ
Backup before moveβ
For critical moves, especially cross-subscription, creating a disk snapshot before the move is a security practice:
# Create OS disk snapshot before move
OS_DISK_ID=$(az vm show \
--resource-group rg-origem \
--name vm-01 \
--query "storageProfile.osDisk.managedDisk.id" \
--output tsv)
az snapshot create \
--name "snapshot-vm-01-osdisk-premove-$(date +%Y%m%d)" \
--resource-group "rg-snapshots" \
--source "$OS_DISK_ID"
8. Decision Makingβ
When to use each move approachβ
| Situation | Approach | Reason |
|---|---|---|
| RG reorganization within same subscription | az resource move | Fast operation, metadata only |
| Consolidate VMs from acquired subscription | az resource move cross-sub | Native move if same tenant |
| Move to region closer to users | Azure Resource Mover | Only supported option for inter-region migration |
| VM with active ADE to another Sub | Move Key Vault together, then VM | KV must be in same Sub |
| Critical VM with minimal downtime to new region | Azure Site Recovery | Continuous replication, controlled failover |
| Recreate VM with same configuration in new region | Capture image + deploy | More control, but requires manual recreation |
| Move dozens of VMs between RGs | PowerShell/CLI batch script | Portal is impractical for high volumes |
Native move vs. recreationβ
| Aspect | Native move (RG/Sub) | Recreate at destination |
|---|---|---|
| Data preserved | Yes (disk data intact) | Depends (need to copy data) |
| Downtime | None (temporary lock) | Necessary to copy data |
| Resource ID | Changes | New ID from the start |
| Risk | Low (atomic operation) | Medium (recreation errors may occur) |
| Time | Minutes | Hours for large VMs |
| Ideal for | VMs with data and complex configuration | New environments or stateless VMs |
9. Best Practicesβ
Always use the move validator before executing.
The validateMoveResources command or portal identifies restrictions before the operation. Discovering that a resource doesn't support move after starting causes unnecessary rollback.
Move all dependent resources in the same operation. A VM without its NIC in the same RG is a VM without network connectivity. Disks without the VM are orphan disks. ARM validates some dependencies, but not all. Manually identify everything that needs to move together.
Document the state before move. Before any cross-subscription move, export current role assignments:
az role assignment list \
--resource-group "rg-origem" \
--output json > rbac-backup-$(date +%Y%m%d).json
This serves as reference to recreate access at destination.
Test resource provider validation on destination subscription.
For cross-subscription, verify in advance that Microsoft.Compute, Microsoft.Network and Microsoft.Storage are registered. Registration can take a few minutes.
For inter-region moves, prefer Azure Resource Mover over manual solutions. Resource Mover manages dependencies automatically, offers a test phase before commit and maintains operation state that allows rollback if necessary.
Plan maintenance windows for cross-subscription moves of critical VMs. Although the move doesn't cause downtime on the running VM, the period when the VM has temporary lock can prevent management operations (scaling, restart, patch application). Communicate the window to dependent teams.
10. Common Errorsβ
| Error | Why it happens | How to avoid |
|---|---|---|
| Move fails due to unregistered resource provider | Destination subscription never used that resource type | Check and register providers before move |
| Move fails due to unsupported resource | Specific disk type or configuration not supported | Use validateMoveResources before executing |
| Alerts and dashboards break after move | Resource ID changes; references become obsolete | Inventory and update all external references |
| RBAC lost without backup | Cross-subscription move deletes role assignments | Export RBAC before move |
| NIC or disk not moved with VM | Incomplete operation, moving only the VM | Always map and include all dependent resources |
| VM with ADE fails cross-sub move | Key Vault must be in same subscription | Move KV first, or decrypt and re-encrypt |
| Backup jobs break after move | Recovery Services Vault points to old Resource ID | Remove VM from backup before move, reconfigure at destination |
| Cross-sub move fails due to subscriptions in different tenants | Native move doesn't work cross-tenant | Use Azure Site Recovery or recreate at destination |
The most critical errorβ
Executing a cross-subscription move without documenting role assignments and without notifying teams that have access to the VM. After the move, all access is lost and teams report that "they can't access the VM". In production, this can cause availability incidents while access is reconfigured.
11. Operation and Maintenanceβ
Post-move checklistβ
# 1. Confirm VM is in new RG/Sub
az vm show \
--resource-group "rg-destino" \
--name "vm-01" \
--query "{Name: name, RG: resourceGroup, Sub: id}" \
--output json
# 2. Verify VM connectivity (if running)
az vm run-command invoke \
--resource-group "rg-destino" \
--name "vm-01" \
--command-id RunShellScript \
--scripts "echo 'VM accessible'"
# 3. Verify disks are associated
az disk list \
--resource-group "rg-destino" \
--query "[?managedBy != null].{Name: name, VM: managedBy}" \
--output table
# 4. Verify Diagnostic Settings (if they exist, they need updating)
az monitor diagnostic-settings list \
--resource "/subscriptions/<sub-destino>/resourceGroups/rg-destino/providers/Microsoft.Compute/virtualMachines/vm-01"
# 5. Verify if VM is still in old Recovery Services Vault
az backup item list \
--resource-group "rg-backup-origem" \
--vault-name "rsv-origem" \
--query "[?properties.friendlyName=='vm-01']" \
--output table
Monitor status of ongoing moveβ
# Large volume moves can take time
# Check recent operation status
az monitor activity-log list \
--resource-group "rg-origem" \
--max-events 10 \
--query "[?operationName.value=='Microsoft.Resources/subscriptions/resourceGroups/moveResources/action'].{Time: eventTimestamp, Status: status.value, Caller: caller}" \
--output table
12. Integration and Automationβ
Bulk move with validation and loggingβ
# Script: Move multiple VMs from one RG to another with logging
param (
[string]$SourceRG,
[string]$DestinationRG,
[string[]]$VMNames
)
$results = @()
foreach ($vmName in $VMNames) {
Write-Output "Processing VM: $vmName"
try {
$vm = Get-AzVM -ResourceGroupName $SourceRG -Name $vmName -ErrorAction Stop
# Collect dependent resources
$resourceIds = @($vm.Id)
$resourceIds += $vm.NetworkProfile.NetworkInterfaces.Id
$resourceIds += $vm.StorageProfile.OsDisk.ManagedDisk.Id
$resourceIds += $vm.StorageProfile.DataDisks.ManagedDisk.Id | Where-Object { $_ -ne $null }
# Export RBAC before move
$rbac = Get-AzRoleAssignment -ResourceGroupName $SourceRG |
Where-Object { $_.Scope -like "*$vmName*" }
$rbac | Export-Csv -Path "rbac-$vmName-$(Get-Date -Format 'yyyyMMdd').csv" -NoTypeInformation
# Execute move
Move-AzResource `
-DestinationResourceGroupName $DestinationRG `
-ResourceId $resourceIds `
-Force
$results += [PSCustomObject]@{
VM = $vmName
Status = "Success"
Message = "Moved to $DestinationRG"
}
Write-Output "VM $vmName moved successfully"
} catch {
$results += [PSCustomObject]@{
VM = $vmName
Status = "Failed"
Message = $_.Exception.Message
}
Write-Error "Failed to move $vmName: $_"
}
}
# Final report
$results | Format-Table -AutoSize
$results | Export-Csv -Path "move-results-$(Get-Date -Format 'yyyyMMdd').csv" -NoTypeInformation
Azure Resource Mover integration via Terraformβ
For region migration as part of an IaC process, Azure Resource Mover can be triggered via Terraform using the azurerm provider:
resource "azurerm_resource_group_template_deployment" "resource_mover" {
name = "resource-mover-deployment"
resource_group_name = azurerm_resource_group.mover.name
deployment_mode = "Incremental"
template_content = jsonencode({
"$schema" = "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#"
contentVersion = "1.0.0.0"
resources = [
{
type = "Microsoft.Migrate/moveCollections"
apiVersion = "2021-08-01"
name = "move-collection-prod"
location = "eastus"
identity = { type = "SystemAssigned" }
properties = {
sourceRegion = "brazilsouth"
targetRegion = "eastus"
}
}
]
})
}
13. Final Summaryβ
Essential points:
- Moving a VM between Resource Groups or Subscriptions is an ARM metadata operation: disk data is not touched, only addressing (Resource ID) changes
- Moving between regions is a physical migration: new resources are created at destination, data is copied, source resources are deleted at the end; use Azure Resource Mover for this operation
- The VM, its NICs and its managed disks must be moved together in the same operation
- Cross-subscription move deletes all role assignments; export RBAC beforehand and recreate at destination
- The move applies a temporary lock on resources during operation; no management actions can be executed on them until completion
- Azure Disk Encryption with Key Vault in another subscription prevents VM move; move Key Vault first or decrypt
Critical differences:
- RG move vs. Subscription move: both change Resource ID, but subscription move also loses RBAC and may require resource provider registration at destination
- Native move vs. Azure Resource Mover: native move is for RG/Subscription (metadata); Resource Mover is for region change (physical migration)
- Azure Resource Mover vs. Azure Site Recovery: Resource Mover is for one-time migration (move once); Site Recovery is for continuous replication with failover (DR)
- Temporary lock during move vs. permanent Resource Lock: move lock is automatic and temporary; Resource Locks are configured by administrator
What needs to be remembered for AZ-104:
- CLI command for move is
az resource move --destination-group --ids - For cross-subscription, add
--destination-subscription-id - Resource providers need to be registered on destination subscription before cross-sub move
- Moving VM between regions requires Azure Resource Mover (not the
az resource movecommand) - The move operation is atomic: if it fails, all resources return to original state
- Check move support:
https://learn.microsoft.com/azure/azure-resource-manager/management/move-support-resources - Minimum required permission:
Microsoft.Resources/subscriptions/resourceGroups/moveResources/action+ Contributor at destination