> ## Content Index
> Fetch the complete content index at: https://www.techielass.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Deploying to Multiple Azure Subscriptions with Terraform Provider Aliases
- URL: https://www.techielass.com/terraform-azure-multi-subscription-provider-aliases/
- Published: 2026-04-21T07:01:16.000Z
- Updated: 2026-04-21T07:01:15.000Z
- Description: Learn how to deploy resources across multiple Azure subscriptions in a single Terraform project using provider aliases. One codebase, one state file, multiple subscriptions.
- Author: Sarah Lean
- Tags: Terraform, Azure

If, like me, you've been using [Terraform](https://www.techielass.com/tag/terraform) for a while, you've probably deployed resources into a single Azure subscription. But what happens when your organization has multiple subscriptions, say, one for development, one for staging, and one for production, and maybe you want to deploy to both development and staging at the same time within one single Terraform project. 

In this post, I'll walk you through exactly how to do that using provider aliases, one of Terraform's most powerful (and beginner-friendly) features.

## The Problem

By default, Terraform's *azurerm* provider targets one Azure subscription. If you need resources in two or three different subscriptions, you might think you need separate Terraform projects for each. That means multiple [state files](https://www.techielass.com/terraform-state-rm/), multiple *terraform apply* runs, and multiple sets of configuration to keep in sync.

There's a better way.

## Terraform Provider Aliases

[Terraform](https://developer.hashicorp.com/terraform?ref=techielass.com) lets you declare **multiple instances** of the same provider, each with a different configuration. You distinguish them using the *alias* argument. Think of it like opening different browser tabs, each logged into a different Azure subscription, they all work independently and simultaneously.

Here's what that looks like in practice.

## Terraform Project Structure

```
├── providers.tf       # Provider configuration (one per subscription)
├── variables.tf       # Input variables
├── main.tf            # Resources for each subscription
├── outputs.tf         # Output values
└── terraform.tfvars   # Your actual subscription IDs and overrides

```

### Step 1: Define Your Variables

First, we need variables for each subscription ID and its region. We also use a 'prefix' variable for consistent naming via the Azure CAF naming provider.

```hcl
variable "subscription_id_1" {
  description = "Azure Subscription ID for environment 1"
  type        = string
}

variable "subscription_id_2" {
  description = "Azure Subscription ID for environment 2"
  type        = string
}

variable "subscription_id_3" {
  description = "Azure Subscription ID for environment 3"
  type        = string
}

variable "location_1" {
  description = "Azure region for Subscription 1"
  type        = string
  default     = "switzerlandnorth"
}

variable "location_2" {
  description = "Azure region for Subscription 2"
  type        = string
  default     = "norwayeast"
}

variable "location_3" {
  description = "Azure region for Subscription 3"
  type        = string
  default     = "swedencentral"
}

variable "prefix" {
  description = "Project prefix fed into CAF naming (e.g. project or workload name)"
  type        = string
  default     = "demo"
}

```

Each subscription gets its own location variable, so you can deploy to different Azure regions if needed.

### Step 2: Configure Multiple Providers

This is the core of the technique. In *providers.tf*, we declare the required providers and then create **three aliased instances** of *azurerm*:

```hcl
terraform {
  required_version = ">= 1.3"

  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = ">= 3.71, < 5.0.0"
    }
  }
}

# ---------- Subscription 1 ----------
provider "azurerm" {
  alias                = "sub1"
  subscription_id      = var.subscription_id_1
  storage_use_azuread  = true
  features {}
}

# ---------- Subscription 2 ----------
provider "azurerm" {
  alias                = "sub2"
  subscription_id      = var.subscription_id_2
  storage_use_azuread  = true
  features {}
}

# ---------- Subscription 3 ----------
provider "azurerm" {
  alias                = "sub3"
  subscription_id      = var.subscription_id_3
  storage_use_azuread  = true
  features {}
}

```

### What's happening here?

- Each provider *azurerm* block has a unique alias — sub1, sub2, sub3.
- Each one points to a different subscription via subscription\_id.
- 'storage\_use\_azuread = true' tells the provider to authenticate to storage data planes using Azure AD instead of shared access keys. This is required when your subscriptions enforce a policy that disables key-based authentication on storage accounts (which is a security best practice).

When you run **terraform init**, Terraform initializes **all three provider instances** in parallel. There's no sequential "switching", it's three simultaneous connections.

### Step 3: Pin Resources to Providers

Now the fun part. In *main.tf*, each resource declares which **provider instance** it belongs to using the *provider* meta-argument:

```hcl
# This ensures we have unique CAF compliant names for our resources.
module "naming_sub1" {
  source  = "Azure/naming/azurerm"
  version = "0.4.2"
  prefix  = [var.prefix]
  suffix  = ["sub1"]
}

module "naming_sub2" {
  source  = "Azure/naming/azurerm"
  version = "0.4.2"
  prefix  = [var.prefix]
  suffix  = ["sub2"]
}

module "naming_sub3" {
  source  = "Azure/naming/azurerm"
  version = "0.4.2"
  prefix  = [var.prefix]
  suffix  = ["sub3"]
}

# ============================================================
#  Subscription 1
# ============================================================

resource "azurerm_resource_group" "sub1" {
  provider = azurerm.sub1
  name     = module.naming_sub1.resource_group.name
  location = var.location_1
}

resource "azurerm_storage_account" "sub1" {
  provider                  = azurerm.sub1
  name                      = module.naming_sub1.storage_account.name_unique
  resource_group_name       = azurerm_resource_group.sub1.name
  location                  = azurerm_resource_group.sub1.location
  account_tier              = "Standard"
  account_replication_type  = "LRS"
  account_kind               = "StorageV2"
  https_traffic_only_enabled  = true
  min_tls_version             = "TLS1_2"
}

# ============================================================
#  Subscription 2
# ============================================================

resource "azurerm_resource_group" "sub2" {
  provider = azurerm.sub2
  name     = module.naming_sub2.resource_group.name
  location = var.location_2
}

resource "azurerm_storage_account" "sub2" {
  provider                  = azurerm.sub2
  name                      = module.naming_sub2.storage_account.name_unique
  resource_group_name       = azurerm_resource_group.sub2.name
  location                  = azurerm_resource_group.sub2.location
  account_tier              = "Standard"
  account_replication_type  = "LRS"
  account_kind               = "StorageV2"
  https_traffic_only_enabled  = true
  min_tls_version             = "TLS1_2"
}

# ============================================================
#  Subscription 3
# ============================================================

resource "azurerm_resource_group" "sub3" {
  provider = azurerm.sub3
  name     = module.naming_sub3.resource_group.name
  location = var.location_3
}

resource "azurerm_storage_account" "sub3" {
  provider                    = azurerm.sub3
  name                        = module.naming_sub3.storage_account.name_unique
  resource_group_name         = azurerm_resource_group.sub3.name
  location                    = azurerm_resource_group.sub3.location
  account_tier                = "Standard"
  account_replication_type    = "LRS"
  account_kind                = "StorageV2"
  https_traffic_only_enabled  = true
  min_tls_version             = "TLS1_2"
}

```

The line *provider = azurerm.sub1* is the magic. It tells Terraform: "Create this resource using the provider instance aliased as sub1," which means it goes into Subscription 1\. Repeat the same pattern for azurerm.sub2 and azurerm.sub3, and you've got resources spread across three subscriptions.

### Step 4: Set Your Variables

Create a *terraform.tfvars* file with your actual subscription IDs:

```hcl
subscription_id_1 = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
subscription_id_2 = "ffffffff-gggg-hhhh-iiii-jjjjjjjjjjjj"
subscription_id_3 = "kkkkkkkk-llll-mmmm-nnnn-oooooooooooo"

# Optional: override the default regions
# location_1 = "switzerlandnorth"
# location_2 = "norwaywest"
# location_3 = "swedencentral"

```

Remember to remove the hashtag on the location lines if you wish to override the default regions. 

If you’re wondering what a `terraform.tfvars` file is and why we’re using it here, I’ve written a separate guide that walks through it in detail.

[Read it here ](https://www.techielass.com/what-is-a-tfvars-file-in-terraform-and-how-do-you-use-it/) 

### Step 5: Deploy

```bash
terraform init       # Initializes all three provider instances
terraform plan -out=tfplan
terraform apply tfplan

```

That's it. One plan, one apply, three subscriptions.

### Step 6: Check Your Outputs

The configuration exposes the created resource names as outputs:

```hcl
# ---------- Subscription 1 ----------
output "sub1_resource_group_name" {
  description = "Resource group name in Subscription 1"
  value       = azurerm_resource_group.sub1.name
}

output "sub1_storage_account_name" {
  description = "Storage account name in Subscription 1"
  value       = azurerm_storage_account.sub1.name
}

# ---------- Subscription 2 ----------
output "sub2_resource_group_name" {
  description = "Resource group name in Subscription 2"
  value       = azurerm_resource_group.sub2.name
}

output "sub2_storage_account_name" {
  description = "Storage account name in Subscription 2"
  value       = azurerm_storage_account.sub2.name
}

# ---------- Subscription 3 ----------
output "sub3_resource_group_name" {
  description = "Resource group name in Subscription 3"
  value       = azurerm_resource_group.sub3.name
}

output "sub3_storage_account_name" {
  description = "Storage account name in Subscription 3"
  value       = azurerm_storage_account.sub3.name
}

```

After *terraform apply*, you'll see all six names printed in your terminal.

## How Does Terraform "Switch" Between Subscriptions?

Short answer: **it doesn't**. There's no switching at all.

When you run \`terraform init\`, Terraform creates an authenticated session for each aliased provider. All three sessions exist simultaneously. When Terraform processes a resource, it looks at the \`provider\` argument and uses the corresponding session. It runs completely parallel, it doesn’t run sequentially. 

All of this is tracked in a **single state file**. You don't need multiple workspaces or separate runs.

## When to Use This Pattern

This approach works well when:

- You manage a small, fixed number of subscriptions.
- You want a single state file and a single *terraform apply* for everything.
- The resources in each subscription are similar but not identical.

If you have many subscriptions or highly dynamic environments, consider using [Terraform modules](https://developer.hashicorp.com/terraform/language/modules?ref=techielass.com) with *for\_each* and passing providers explicitly.

## Wrapping Up

Deploying across multiple Azure subscriptions doesn't require multiple Terraform projects. With provider aliases, you get:

- One codebase to maintain.
- One state file to track everything.
- One *terraform apply* to deploy across all subscriptions.

The key takeaway: *alias* creates named instances of a provider, and *provider = azurerm.<alias>* on a resource tells Terraform which instance (and therefore which subscription) to use. That's all there is to it.

The full source code for this project is available on [GitHub](https://github.com/weeyin83/terraform-azure-multi-subscription-deployment.git?ref=techielass.com). Happy deploying!