In most Terraform projects you’ll probably be deploying and managing Azure resources within a single Azure Subscription. However, it’s common to structure things across multiple Azure Subscriptions in certain cases. One of these cases in when deploying a Hub and Spoke networking model and separating DEV and PROD environments into separate Azure Subscriptions. This is a case where you may end up having a single Terraform project manage some of the Resource Groups, VNets, and other resources within a single Terraform project managing the Governance or Networking for your environments. There are of course other cases where you may need to manage Azure resources across multiple Azure Subscriptions.


Define Multiple Azure Providers with Different Service Principals

At these times, you will need to support multiple instances of the azurerm Terraform Provider each targeting a different Azure Subscription. To do this, you can utilize the alias attribute on the provider block in Terraform to have multiple azurerm providers configured for different Azure Subscription credentials (client id / secret).

This become necessary when you have a separate Azure Service Principal (client id / secret) for each Azure Subscription with permissions to manage Azure resources each in only a single Azure Subscription. This helps increase the security of the Service Principals you use to setup your Infrastructure as Code (IaC) deployments.

Below is an example of 2 different provider blocks for the azurerm Terraform provider with the alias attribute set to differentiate them, as they each have different Azure Service Principals configured:

terraform {
  required_providers {
    azurerm = {
      source = "hashicorp/azurerm"
      version = "=3.0.0"
    }
  }
}

provider "azurerm" {
  alias = "dev"

  subscription_id = var.dev_sub_id
  tenant_id       = var.dev_tenant_id
  client_id       = var.dev_client_id
  client_secret   = var.dev_client_secret

  features {}
}

provider "azurerm" {
  alias = "prod"

  subscription_id = var.prod_sub_id
  tenant_id       = var.prod_tenant_id
  client_id       = var.prod_client_id
  client_secret   = var.prod_client_secret

  features {}
}

Keep in mind that the above example uses Input Variables passed to the Terraform Project to define the values that are used to configure the Azure Service Principals to use for each of the azurerm Provider instances authenticate with different Azure Subscriptions.

Deploy to Multiple Azure Subscriptions with Terraform

Once you have multiple azurerm Terraform Providers configured in your Terraform project with the alias attribute set for each, you can then use the provider attribute on resource blocks to tell that resource block to explicitly use a specific azurerm Terraform provider with the alias specified.

Below is an example of a single Terraform project with some code that manages a couple Azure resources across multiple Azure Subscriptions utilizing the azurerm Terraform providers defined in the code example above:

resource azurerm_resource_group "prod_rg" {
  provider = azurerm.prod
  name     = "E1-PROD-RG"
  location = "eastus"
}

resource azurerm_virtual_network "prod_vnet" {
  provider = azurerm.prod
  name     = "E1-PROD-VNET"
  location = azurerm_resource_group.prod_rg.location
  location = azurerm_resource_group.prod_rg.name

  # Other attributes defined here...
}

resource azurerm_resource_group "dev_rg" {
  provider = azurerm.dev
  name     = "C1-DEV-RG"
  location = "centralus"
}

resource azurerm_virtual_network "dev_vnet" {
  provider = azurerm.dev
  name     = "C1-DEV-VNET"
  location = azurerm_resource_group.prod_rg.location
  location = azurerm_resource_group.prod_rg.name

  # Other attributes defined here...
}

With the resource.provider attribute you’re telling your Terraform code which specific Terraform Provider to use for that resource. By defining multiple azurerm providers with the alias attribute, you are able to setup multiple instances of the azurerm provider to be used within a single Terraform project. This can be done with the azurerm provider or any other Terraform provider as well.

Passing Terraform Provider Alias to Modules

When you write modular and reusable Terraform code with Terraform Modules, the code within the module will likely just be referencing the azurerm Terraform Provider without the use of the provider.alias specifed. Be default, this will cause the Terraform Module to utilize the single default azurerm provider defined. If you have multiple azurerm providers defined using the alias attribute, you can use the providers attribute to configure the Terraform Module being used to target a specific provider instance.

Using the providers attribute on a module block can enable you to still utilize multiple azurerm Terraform Providers in a project that is developed using Terraform Modules.

Below is an example of defining multiple module blocks, each with a different azurerm provider instance specified for it to use:

module "sample-dev" {
  source = "./sample-module"
  providers = {
    azurerm = azurerm.dev
  }
}

module "sample-prod" {
  source = "./sample-module"
  providers = {
    azurerm = azurerm.prod
  }
}

When using the module.providers attribute to tell the Module which Terraform Provider instance to use, you define it using an object with syntax that allows for multiple providers to be specified for the module. The reason for this is that a single Terraform module could have code in it that manages multiple resources that are a part of multiple different Terraform providers. This enables you to explicitly specify all the different Terraform providers for that module to use in case you have multiple instances of multiple different types of Terraform Providers used in your single Terraform project.

Deploy to Multiple Azure Subscriptions with a “Default” azurerm Provider

The previous examples shows how to define multiple azurerm Terraform Providers using the alias attribute and then explicitly tell a resource or module which Terraform Provider instance to use. However, what if you need to define a “Default” azurerm Terraform Provider you want Terraform to automatically use first, unless you explicitly tell it which provider to use for specific resources or modules? Well, you can easily do this by defining a azurerm Terraform Provider without the alias attribute set and this will be the “Default” provider.

Below is an example of this used:

terraform {
  required_providers {
    azurerm = {
      source = "hashicorp/azurerm"
      version = "=3.0.0"
    }
  }
}

# This is the "Default" provider
provider "azurerm" {
  subscription_id = var.dev_sub_id
  tenant_id       = var.dev_tenant_id
  client_id       = var.dev_client_id
  client_secret   = var.dev_client_secret

  features {}
}

# This provider uses the "prod" alias
provider "azurerm" {
  alias = "prod"

  subscription_id = var.prod_sub_id
  tenant_id       = var.prod_tenant_id
  client_id       = var.prod_client_id
  client_secret   = var.prod_client_secret

  features {}
}

# Use the "Default" azurerm provider
resource azurerm_resource_group "dev_rg" {
  name     = "C1-DEV-RG"
  location = "centralus"
}

module "sample-dev" {
  source = "./sample-module"
}

# Use the azurerm provider with the "prod" alias
resource azurerm_resource_group "prod_rg" {
  provider = azurerm.prod
  name     = "E1-PROD-RG"
  location = "eastus"
}

module "sample-prod" {
  source = "./sample-module"
  providers = {
    azurerm = azurerm.prod
  }
}

Happy coding!

Microsoft MVP

Chris Pietschmann is a Microsoft MVP (Azure & IoT) and HashiCorp Ambassador (2021) with 20+ years of experience designing and building Cloud & Enterprise systems. He has worked with companies of all sizes from startups to Fortune 100. He is also a Microsoft Certified Azure Solutions Architect and developer, a Microsoft Certified Trainer (MCT), and Cloud Advocate. He has a passion for technology and sharing what he learns with others to help enable them to learn faster and be more productive.