Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Terraform Provider Aliases for Multi-Cloud

By Kokil Thapa | Last reviewed: September 2026

Terraform Provider Aliases for Multi-Cloud solve a narrow but painful problem: one codebase must create resources in more than one cloud account, region, or vendor in the same run. A single default aws or azurerm block is not enough when your app spans Mumbai and Frankfurt, or AWS plus Azure for failover. Aliases give each target its own authenticated provider instance while keeping related infrastructure in one plan. If you already treat infrastructure as code, see our Terraform infrastructure-as-code practical guide for the baseline patterns this article builds on.

What are Terraform Provider Aliases for Multi-Cloud?

A provider alias is a named duplicate of a provider configuration. Terraform allows only one default provider per type in a module. Any additional instance needs alias = "name".

In multi-cloud work, aliases usually map to one of these targets:

  • A second AWS region (primary in ap-south-1, DR in eu-west-1)
  • A separate AWS account (prod vs shared services)
  • A different vendor entirely (aws, azurerm, google in one root module)
  • A partner or client subscription you manage from one repo

Aliases do not replace multi-cloud Terraform state design. They only tell Terraform which credentials and endpoint to use for a given resource. State still lives in one backend unless you split stacks on purpose.

Multi-Cloud Root Moduleprovider awsap-south-1 defaultprovider aws.eualias eu-west-1provider azurermalias secondarySingle terraform plan / applyOne state file, many provider configsS3 + CloudFrontRDS read replicaAzure Blob backup
Terraform Provider Aliases for Multi-Cloud: one root module, multiple named provider instances, one coordinated plan.

On production systems I maintain, aliases show up most often during migration. A client keeps the old VPS while the new cloud stack comes online. Aliases let both environments coexist in one module until cutover. That pattern pairs well with controlled website and infrastructure migration where downtime must stay minimal.

How do you configure provider aliases in Terraform?

Start in the root module. Declare providers in required_providers, then add one block per alias. Pin versions—drift here breaks plans silently across clouds. Our provider version pinning guide covers that discipline.

Step 1: Declare providers and versions

terraform {
  required_version = ">= 1.5.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 4.0"
    }
    google = {
      source  = "hashicorp/google"
      version = "~> 6.0"
    }
  }
}

Step 2: Configure default and aliased providers

# Default AWS — Mumbai primary
provider "aws" {
  region = "ap-south-1"

  default_tags {
    tags = {
      Environment = "production"
      ManagedBy   = "terraform"
    }
  }
}

# Aliased AWS — Ireland DR region
provider "aws" {
  alias  = "dr"
  region = "eu-west-1"

  default_tags {
    tags = {
      Environment = "production"
      Role        = "dr"
      ManagedBy   = "terraform"
    }
  }
}

# Aliased Azure — backup target
provider "azurerm" {
  alias = "backup"
  features {}

  subscription_id = var.azure_backup_subscription_id
}

# Aliased GCP — analytics project
provider "google" {
  alias   = "analytics"
  project = var.gcp_analytics_project
  region  = "asia-south1"
}

Step 3: Attach aliases to resources

Resources without a provider argument use the default. Everything else must name the alias explicitly.

resource "aws_s3_bucket" "primary_assets" {
  bucket = "app-assets-ap-south-1"
}

resource "aws_s3_bucket" "dr_replica" {
  provider = aws.dr
  bucket   = "app-assets-eu-west-1"
}

resource "azurerm_storage_account" "cold_backup" {
  provider                 = azurerm.backup
  name                     = "appbackupstore"
  resource_group_name      = azurerm_resource_group.backup.name
  location                 = "southeastasia"
  account_tier             = "Standard"
  account_replication_type = "GRS"
}

resource "google_storage_bucket" "logs" {
  provider = google.analytics
  name     = "app-logs-asia-south1"
  location = "ASIA-SOUTH1"
}

Official reference: HashiCorp documents provider configuration and the alias meta-argument in the Terraform provider configuration docs. Treat that page as the source of truth when syntax changes between releases.

Provider Alias Wiringterraform {}required_providersprovider awsdefault configprovider awsalias = "dr"provider azurermalias = "backup"terraform init downloads all provider pluginsresource (default)provider = awsresource (aliased)provider = aws.drmodule blockproviders = { ... }Missing provider = line sends resource to wrong cloud or fails plan
Configuration flow: declare providers, assign aliases, then bind each resource or module to the correct instance.

Authentication across clouds

Each aliased provider resolves credentials independently. On a CI runner, that usually means distinct environment variables or OIDC roles per account.

  1. AWS default: AWS_ACCESS_KEY_ID / IAM role for account A
  2. AWS alias dr: assume_role block inside the provider, or a named profile
  3. Azure alias: ARM_SUBSCRIPTION_ID, ARM_CLIENT_ID, tenant vars scoped to backup sub
  4. GCP alias: service account JSON path or Workload Identity Federation

Never hard-code secrets in provider blocks. Use environment variables, Vault, or your pipeline secret store. Multi-cloud secret handling overlaps with our multi-cloud secrets management write-up.

When should you use provider aliases instead of workspaces or separate root modules?

Aliases, workspaces, and separate root modules all split configuration. They solve different problems. Pick the wrong one and you get brittle state or unclear ownership.

ApproachBest forState layoutMulti-cloud fit
Provider aliasesSame logical app, multiple regions/accounts/vendors in one deployment unitSingle state (or one state per stack that intentionally spans clouds)Excellent — native multi-vendor support
WorkspacesIdentical code, different env vars (dev/stage/prod) on one cloudOne backend, workspace-prefixed resourcesPoor — one workspace still shares provider config unless you use vars heavily
Separate root modulesIndependent lifecycles, different teams, strict blast-radius isolationOne state per root moduleGood — clearest boundaries, more orchestration overhead
Terragrunt / stack wrappersMany similar roots with shared remote state configMultiple states, DRY backend configGood at scale — see Terragrunt for DRY Terraform

Use aliases when resources must be created together and reference each other. Example: primary RDS in AWS Mumbai plus read replica in AWS Ireland plus Azure Blob for backup—all in one apply so outputs wire cleanly.

Use separate roots when teams, approval paths, or blast radius differ. A networking team owning hub VPCs should not share state with an app team shipping Lambda functions. Our multi-cloud architecture guide walks through that split in more detail.

Workspaces alone cannot give you a second cloud vendor. They switch context inside one provider setup. Many engineers confuse the two and then wonder why terraform workspace select prod does not reach Azure.

Alias vs Workspace vs Split StateProvider AliasesMulti-regionMulti-accountMulti-vendorWorkspacesdev / stage / prodSame provider configNot for multi-cloudSeparate RootsTeam boundariesOwn state eachRemote state linksChoose aliases when one coordinated apply must touch multiple cloudsDR pairAWS + AWS aliasHybrid backupAWS + Azure aliasSplit ownershipTwo root modulesAvoid workspaces as a multi-cloud strategy
Decision lens: aliases for coordinated multi-target applies; workspaces for env toggles; separate roots for hard isolation.

For Nepal-based teams weighing cloud mix, cost and latency often push primary workloads to one region with DR abroad. Aliases keep that topology visible in one file instead of scattered shell scripts. Compare broader strategy in hybrid cloud vs multi-cloud before you commit to three vendors.

How do you pass aliased providers to Terraform modules?

Child modules do not inherit parent aliases automatically. You must declare configuration_aliases inside the module and map them in the parent's providers argument. This trips experienced engineers because default provider passing feels implicit until it breaks.

Inside the child module

# modules/dr-bucket/main.tf
terraform {
  required_providers {
    aws = {
      source                = "hashicorp/aws"
      configuration_aliases = [aws]
    }
  }
}

variable "bucket_name" {
  type = string
}

resource "aws_s3_bucket" "this" {
  bucket = var.bucket_name
}

Note: the module never sets alias. It accepts whichever aws instance the parent injects.

Calling the module from root

module "dr_bucket" {
  source = "./modules/dr-bucket"

  providers = {
    aws = aws.dr
  }

  bucket_name = "company-dr-assets"
}

For multi-cloud modules that need both AWS and Azure, list both in configuration_aliases and map each key in the parent.

# modules/hybrid-backup/main.tf
terraform {
  required_providers {
    aws = {
      source                = "hashicorp/aws"
      configuration_aliases = [aws]
    }
    azurerm = {
      source                = "hashicorp/azurerm"
      configuration_aliases = [azurerm]
    }
  }
}
module "hybrid_backup" {
  source = "./modules/hybrid-backup"

  providers = {
    aws     = aws
    azurerm = azurerm.backup
  }
}

Reusable modules belong in your org registry or a versioned git path. Patterns for that structure appear in Terraform modules for reusable infrastructure. Validate HCL with a JSON formatter when generating provider maps from scripts—bad escaping breaks plans fast.

HashiCorp documents the module providers meta-argument in the official module provider passing guide. Read it before abstracting a shared multi-cloud module library.

Implicit vs explicit default passing

If a module only uses the default provider and does not declare configuration_aliases, Terraform passes the parent's default automatically. The moment a module needs a non-default alias, implicit passing stops. Every child module in the chain must re-export the alias requirement upward.

Deep module trees get verbose. Some teams generate the providers map with Terragrunt or a thin wrapper. Others split stacks instead of nesting five modules deep. Both are valid; pick based on who owns reviews.

What are common mistakes with Terraform provider aliases in multi-cloud setups?

Most alias failures I see are not syntax errors. They are wrong assumptions about state, credentials, or module boundaries.

Forgetting the provider meta-argument on resources

A resource without provider = aws.dr lands in the default region. You think you built DR in Ireland; you built two buckets in Mumbai. Add lint rules or Checkov scans on Terraform to catch unannotated resources in multi-alias roots.

Mixing unrelated lifecycles in one state

Aliases encourage one big state file. That is fine for a tightly coupled app stack. It is dangerous when unrelated platforms share it. One bad terraform destroy can touch production AWS and a client Azure sub together. Split state when ownership or risk profiles differ. Follow safe Terraform state management practices either way.

Duplicated provider config instead of dynamic blocks

Teams copy-paste ten nearly identical provider "aws" blocks for ten regions. Use for_each on a map of regions where the provider supports it, or generate blocks with Terraform dynamic blocks. Provider blocks themselves cannot use for_each in older patterns, but Terraform 1.x allows provider "aws" with aliases via generated config in some wrapper tools. Keep the root readable.

Version skew across clouds

AWS provider 5.x and Azure 4.x release on different cadences. Pin each independently in required_providers. A blanket >= 1.0 constraint invites breaking schema changes mid-sprint.

Ignoring plan scope and blast radius

A plan touches every provider configured. CI must hold credentials for all of them even if the change looks AWS-only. Scope plans with -target only for emergencies—not daily workflow. Prefer smaller stacks instead.

Multi-Cloud Alias GotchasWrong default providerResource created in primary regionMissing module providersPlan fails: provider not passedOversized single stateDestroy hits all cloudsCI creds incompletePlan auth fails on Azure aliasFix patternExplicit provider on every non-default resourceSplit state by team / blast radius
Typical alias failures: silent wrong-region creates, module wiring gaps, and over-broad state files.

Real-world pattern: app plus DR plus backup

On a booking platform I helped operate, primary compute stayed on one cloud region. DR replicas and object storage spanned a second region and vendor. Aliases let Terraform output the primary database endpoint and the DR replica hostname in one apply output block—useful for runbooks and production booking infrastructure that cannot afford silent drift.

Pair aliases with remote state locking. Multi-cloud plans run longer; concurrent applies corrupt state more painfully. Governance policies from multi-cloud policy as code should block public exposure regardless of which alias created a resource.

Variables, outputs, and workspace hygiene

Keep cloud-specific IDs in clearly named variables. Use outputs to expose only what downstream stacks need. Our Terraform variables and outputs guide applies unchanged—aliases do not replace disciplined naming.

For environment separation inside one cloud, combine aliases with Terraform workspaces and environments. Example: default AWS for app, aliased AWS for shared DNS, workspace selects prod vs staging tags. Do not expect workspaces alone to select Azure vs AWS.

When exploring OpenTofu as a fork, alias syntax stays compatible for most roots. Review OpenTofu vs Terraform before switching provider sources in regulated workloads.

Key Takeaways

  • Define one provider block per cloud target; add alias for every non-default instance.
  • Bind resources with provider = aws.dr and modules with an explicit providers map.
  • Declare configuration_aliases in any module that accepts injected providers.
  • Prefer aliases for coordinated multi-cloud applies; split root modules when blast radius or ownership differs.
  • Pin provider versions per vendor and scan plans in CI with credentials for every configured alias.
  • Keep state boundaries intentional—aliases wire providers; they do not replace sound multi-cloud state design.

People Also Ask

Can one Terraform resource use multiple providers?

No. Each resource block binds to exactly one provider instance. Cross-cloud relationships use references between resources—output from an AWS resource fed into an Azure resource—not a multi-provider resource block. That one-resource-one-provider rule keeps dependency graphs predictable.

How many provider aliases can Terraform have?

There is no hard cap in Terraform core. Practical limits are plan time, credential management, and human readability. Beyond a handful of aliases, consider generated config or split stacks. Very large alias sets belong in Terragrunt or separate repos per domain.

Do provider aliases work with OpenTofu?

Yes for mainstream patterns. OpenTofu maintains compatibility with Terraform language constructs including alias, providers module maps, and configuration_aliases. Always run tofu validate after porting because provider registry paths may differ in air-gapped installs.

What is the difference between provider alias and assume_role?

An alias names a provider configuration inside Terraform. assume_role is an AWS provider argument that fetches temporary credentials for a different IAM role. You often combine them: an aliased provider "aws" block with its own assume_role targets a DR account while the default provider stays in the primary account.

Ship multi-cloud infrastructure without alias surprises

Terraform Provider Aliases for Multi-Cloud are the correct tool when one deployment unit must reach multiple regions, accounts, or vendors in a single plan. Configure named provider blocks, pin versions, wire modules explicitly, and split state when blast radius grows beyond one team. Start small—primary plus one DR alias—before you orchestrate three vendors from one root module. If you want help designing IaC for a migration or DR setup on Linux cloud infrastructure, or reviewing an existing Terraform layout before production cutover, contact us for a technical review.

Frequently Asked Questions

They are extra provider blocks with an alias argument—one per cloud account, region, or vendor. Terraform allows only one default provider per type in a module; every additional target needs alias = "name". Resources and modules reference the correct instance via provider = aws.dr or a providers map, so one root module can coordinate AWS, Azure, and GCP in a single plan without splitting your codebase.

Start in the root module. Declare providers and pin versions in required_providers, then add one provider block per alias alongside your default. Bind each resource with provider = aws.dr or similar, and pass aliased providers into child modules using configuration_aliases plus an explicit providers map. HashiCorp’s provider configuration docs are the authoritative reference when syntax shifts between releases.

No. Each resource block binds to exactly one provider instance. Cross-cloud relationships use references between resources—an AWS output feeding an Azure input—not a multi-provider resource block.

Use aliases when the same logical app must create resources across regions, accounts, or vendors in one coordinated apply—primary RDS in Mumbai, replica in Ireland, Azure Blob backup, all wired via outputs. Workspaces suit identical code with different env vars on one cloud; they cannot reach a second vendor. Separate root modules fit different teams, approval paths, or blast-radius isolation, such as a networking hub VPC owned apart from application Lambda stacks.

Child modules do not inherit parent aliases automatically. Inside the module, declare configuration_aliases in required_providers for each provider type the module accepts. In the parent, map them explicitly: providers = { aws = aws.dr }. For multi-cloud modules needing both AWS and Azure, list both in configuration_aliases and map each key. If a module only uses the default provider and declares no configuration_aliases, Terraform passes the parent default implicitly—until any non-default alias is required.

Each aliased provider resolves credentials independently. On a CI runner that usually means distinct environment variables or OIDC roles per account: AWS default via AWS_ACCESS_KEY_ID or an IAM role, AWS alias dr via assume_role or a named profile, Azure alias via ARM_SUBSCRIPTION_ID and related tenant vars, GCP alias via service account JSON or Workload Identity Federation. Never hard-code secrets in provider blocks; use environment variables, Vault, or your pipeline secret store.

No. Aliases only tell Terraform which credentials and endpoint to use for a given resource. State still lives in one backend unless you split stacks on purpose. Aliases encourage a single state file for tightly coupled stacks, which works when resources reference each other, but unrelated platforms sharing one state raise destroy risk. Split state when ownership or risk profiles differ, and pair aliases with remote state locking because multi-cloud plans run longer.

The painful ones are assumptions, not syntax. Forgetting provider = aws.dr creates DR resources in the default region silently. Mixing unrelated lifecycles in one state lets one terraform destroy touch production AWS and a client Azure subscription together. Copy-pasting ten nearly identical provider blocks instead of dynamic or generated config hurts readability. Blanket version constraints invite breaking schema changes. Plans touch every configured provider, so CI needs credentials for all of them even when the diff looks AWS-only.

Terraform core sets no hard cap. Practical limits are plan duration, credential management, and human readability. Beyond a handful of aliases, consider generated config or split stacks.

Yes for mainstream patterns. OpenTofu maintains compatibility with Terraform language constructs including alias, providers module maps, and configuration_aliases. Always run tofu validate after porting because provider sources or edge-case behaviour can still differ in regulated workloads.

Workspaces switch context inside one provider setup—useful for dev, stage, and prod tags on identical code. They do not give you a second cloud vendor or a second region with separate credentials. Many engineers run terraform workspace select prod and wonder why Azure never appears. Combine workspaces with aliases for environment separation inside one cloud, but reach multiple vendors or regions through aliased provider blocks, not workspace selection alone.

If a child module only uses the default provider and declares no configuration_aliases, Terraform passes the parent’s default automatically—implicit passing. The moment a module needs a non-default alias, implicit passing stops. Every child module in the chain must re-export the alias requirement upward via configuration_aliases, and the root must supply an explicit providers map. Deep module trees get verbose; some teams generate provider maps with Terragrunt or split stacks instead of nesting five modules deep.

Pin each vendor independently in required_providers. The article’s pattern uses aws ~> 5.0, azurerm ~> 4.0, and google ~> 6.0 alongside terraform >= 1.5.0. AWS 5.x and Azure 4.x release on different cadences, so a blanket >= 1.0 constraint invites breaking schema changes mid-sprint. Version drift here breaks plans silently across clouds; treat provider pinning as non-negotiable discipline in any multi-alias root.

Primary compute in one cloud region, DR replicas and object storage in a second region and vendor—a pattern seen on booking platforms where runbooks need both endpoints from one apply. Aliases let Terraform output the primary database endpoint and DR replica hostname together. Pair that with remote state locking, governance policies blocking public exposure regardless of which alias created a resource, and intentional state boundaries so a DR stack does not share destroy scope with unrelated client subscriptions.

A resource without provider = aws.dr lands in the default region—you think you built DR in Ireland but created two buckets in Mumbai. Add lint rules or Checkov scans on Terraform to catch unannotated resources in multi-alias roots. Scope plans in CI with credentials for every configured alias, because a change that looks AWS-only still evaluates all providers. Reserve terraform plan -target for emergencies, not daily workflow; prefer smaller stacks when blast radius grows unwieldy.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: