In cloud environments, especially in Azure, infrastructure is often spread across multiple subscriptions for security and organizational reasons.
Hub and Spoke Topology is a classic pattern where:
-
- The Hub VNet (shared resources, security services, DNS zones etc.) lives in its own subscription.
- The Spoke VNets (application workloads) live in different subscriptions, each managing their own state files.
While everything works smoothly for independent deployments, the real problem starts when you need cross-subscription interactions, like:
-
- ✅ VNet Peering between Hub and Spoke (both sides need peering objects)
- ✅ Private Endpoint + Private DNS Zones (Private Endpoint in spoke, DNS zone in hub)
By default, Terraform executes operations only against a single provider configuration (in our case, a single Azure subscription).
So, how can we create resources in two different subscriptions at the same time from within the spoke configuration?
Answer → Terraform alias provider.
📌 Scenario — VNet Peering between Hub and Spoke
-
- Hub VNet → Deployed already → In Subscription A (critical, no change expected)
- Spoke VNet → Deployed already → In Subscription B
Now from Spoke side (where we are working right now), we need:
-
- Peering from Spoke → Hub → Simple, as spoke is local subscription
- Peering from Hub → Spoke → Problematic, because Hub is in a different subscription
✅ Solution → Use Terraform Provider Alias for Hub Subscription
📦 Terraform Code Example
Step 1: Define Two Providers (One Default, One with Alias)
provider "azurerm" {
features {}
subscription_id = var.spoke_subscription_id
}
provider "azurerm" {
alias = "hub"
features {}
subscription_id = var.hub_subscription_id
}
✅ The default provider (without alias) will be used for Spoke.
✅ The aliased provider (“hub”) will be used for Hub Subscription.
Step 2: Create Peering from Spoke → Hub (Using Default Provider)
resource "azurerm_virtual_network_peering" "spoke_to_hub" {
name = "SpokeToHub"
resource_group_name = var.spoke_vnet_rg
virtual_network_name = var.spoke_vnet_name
remote_virtual_network_id = var.hub_vnet_id
allow_forwarded_traffic = true
allow_gateway_transit = false
use_remote_gateways = false
}
This will create the peering on the Spoke side.
Step 3: Create Peering from Hub → Spoke (Using provider = azurerm.hub)
resource "azurerm_virtual_network_peering" "hub_to_spoke" {
provider = azurerm.hub
name = "HubToSpoke"
resource_group_name = var.hub_vnet_rg
virtual_network_name = var.hub_vnet_name
remote_virtual_network_id = var.spoke_vnet_id
allow_forwarded_traffic = true
allow_gateway_transit = true
use_remote_gateways = true
}
✅ Both peering objects can now be created from Spoke Terraform plan/apply.
📘 Private DNS Zone Example (Hub) + Private Endpoint (Spoke)
resource "azurerm_private_dns_a_record" "private_endpoint_dns" {
provider = azurerm.hub
name = "myservice"
zone_name = var.private_dns_zone_name
resource_group_name = var.hub_dns_rg
ttl = 300
records = [var.private_endpoint_ip]
}
✅ Another real-world use case where alias comes handy.
📌 Summary
| Resource Type | Subscription | Provider Usage |
|---|---|---|
| Spoke → Hub Peering | Spoke | Default provider |
| Hub → Spoke Peering | Hub | provider = azurerm.hub |
| Private Endpoint | Spoke | Default provider |
| Private DNS A Record | Hub | provider = azurerm.hub |
📦 Using Terraform Alias in Azure DevOps Pipelines (Single Subscription Context)
In enterprise CI/CD pipelines like Azure DevOps Pipelines, Terraform plans and applies are typically targeted at a single subscription at a time.
So how do you use alias in this case?
Answer → By authenticating the alias provider separately using Service Principal details.
-
- Create a separate SPN for Hub Subscription (Contributor/Network Contributor access).
- Store its
client_id,client_secret,tenant_id, andsubscription_idin pipeline variables or secrets. - Use these for alias provider authentication in Terraform.
provider "azurerm" {
alias = "hub"
features {}
subscription_id = var.hub_subscription_id
tenant_id = var.hub_tenant_id
client_id = var.hub_client_id
client_secret = var.hub_client_secret
}
✅ Azure DevOps Pipeline Example
trigger:
- main
pool:
vmImage: 'ubuntu-latest'
variables:
spoke_subscription_id: 'xxxxxxxx-xxxx'
hub_subscription_id: 'yyyyyyyy-yyyy'
hub_tenant_id: 'zzzzzzzz-zzzz'
hub_client_id: 'aaaaaaaa-aaaa'
hub_client_secret: $(hub_client_secret)
steps:
- task: TerraformInstaller@1
inputs:
terraformVersion: '1.5.7'
- task: AzureCLI@2
displayName: 'Terraform Plan'
inputs:
azureSubscription: 'Azure-Spoke-ServiceConnection'
scriptType: bash
scriptLocation: inlineScript
inlineScript: |
terraform init
export ARM_HUB_CLIENT_ID=$(hub_client_id)
export ARM_HUB_CLIENT_SECRET=$(hub_client_secret)
export ARM_HUB_TENANT_ID=$(hub_tenant_id)
terraform plan -out=tfplan
- task: AzureCLI@2
displayName: 'Terraform Apply'
inputs:
azureSubscription: 'Azure-Spoke-ServiceConnection'
scriptType: bash
scriptLocation: inlineScript
inlineScript: |
terraform apply tfplan
✅ Hub alias provider will authenticate using SPN.
✅ Spoke default provider will authenticate using Service Connection.
✅ When to Use Alias
-
- Cross-subscription VNet Peering
- Private DNS Zone management across subscriptions
- Shared resources residing in different subscriptions
- Hybrid cloud scenarios (Azure Global + Azure China, etc.)
✅ Conclusion
Terraform alias is not just a feature — it’s a life-saver when dealing with complex enterprise environments like Hub & Spoke topologies in Azure.
Without alias:
-
- 🚨 Running multiple plans and applies becomes mandatory
- 🚨 You risk making changes in critical Hub environments manually
- 🚨 Configuration drift and human errors increase
With alias, you achieve:
-
- ✅ Single plan/apply covering both Spoke and Hub resources
- ✅ Avoid touching Hub’s Terraform state again and again
- ✅ Seamless Azure DevOps Pipeline integration for automation
→ Use Terraform alias smartly and safely automate multi-subscription Azure deployments with ease.