Provider aliases let you configure the same provider more than once, each with its own credentials and settings, and reference them per resource. In this article, we’ll show how to declare aliases, pass them to modules, and avoid common pitfalls.
A Terraform provider alias is a named, secondary provider configuration that lets you use the same provider with different settings in one run. It is used to target multiple regions, accounts, or endpoints without duplicating code. Aliases improve clarity when managing cross-region or cross-account deployments.
You define it in a provider block with alias, then reference it from resources or modules. For example: provider "aws" { region = "eu-central-1" } is the default, and provider "aws" { alias = "use1"; region = "us-east-1" } is an alias, used as provider = aws.use1 in a resource or passed to a module via providers = { aws = aws.use1 }.
The default configuration has no alias, so resources without an explicit provider use it automatically. 
If all provider blocks for a provider type use aliases, Terraform creates an implied empty default configuration. Any resource without an explicit provider will use that empty default, which may lead to errors if required settings (e.g., region) are missing. 
When you deploy identical infrastructure to multiple regions, you don’t want to copy-paste whole configurations. A cleaner pattern is to configure one default provider plus one or more aliased provider instances, then point individual resources at the right instance using the provider meta-argument.
A provider instance is identified by its type and optional alias. Resources and data sources bind to a specific instance by name, so you get explicit control over where something is created — without forking the whole module.
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}
# Default provider: us-west-2
provider "aws" {
  region = "us-west-2"
}
# Aliased provider: us-east-1
provider "aws" {
  alias  = "use1"
  region = "us-east-1"
}
# Uses the default provider (us-west-2)
resource "aws_s3_bucket" "logs_west" {
  bucket = "my-logs-west-123456"
}
# Uses the aliased provider (us-east-1)
resource "aws_s3_bucket" "logs_east" {
  provider = aws.use1
  bucket   = "my-logs-east-123456"
}
# Data sources can also target a specific instance
data "aws_caller_identity" "east" {
  provider = aws.use1
}If you later add eu-central-1, you just create another aliased provider and point selected resources at it.
Inside a child module, resources typically refer to the unaliased local provider name (e.g., just aws). The parent decides which instance of the provider the child should use, by supplying a providers map in the module block.
This separation lets the same module work in different regions or accounts just by wiring different aliases in the parent.
Parent module (root)
# Provider instances at the root
provider "aws" {
  region = "eu-west-1"
}
provider "aws" {
  alias  = "dr"
  region = "eu-central-1"
}
module "app_primary" {
  source = "./modules/app"
  # Map the child's 'aws' to the root's default instance
  providers = {
    aws = aws
  }
  env = "prod"
}
module "app_dr" {
  source = "./modules/app"
  # Map the child's 'aws' to the root's 'aws.dr' instance
  providers = {
    aws = aws.dr
  }
  env = "prod-dr"
}Child module (./modules/app)
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
      configuration_aliases = [ aws.dr ]
    }
  }
}
# Note: no provider blocks here; the parent injects them.
resource "aws_s3_bucket" "app" {
  bucket = "my-app-${var.env}-123456"
}
variable "env" {
  type = string
}The child module is portable and testable, since it never hard-codes region or account. The parent remains the single place where environment wiring lives.
Using provider aliases is a great way to target multiple regions or accounts without duplicating code, but it’s also easy to make small mistakes that lead to big surprises at apply time.Â
Here are the common pitfalls and how to solve them:
| Pitfall | Description | Solution | 
| Using a string in provider | Terraform treats "aws.use1"as plain text, not a provider instance | Use a reference, e.g., provider = aws.use1(no quotes) | 
| Expecting a dynamic/computed provider | The providermeta-argument can’t be conditional or computed | Split resources and point each to a concrete instance (e.g., separate primaryanddrresources) | 
| Renaming an alias without state migration | Changing aliaschanges the provider address and may trigger recreation | Plan a state migration (e.g., terraform state replace-provider/state mv) before applying | 
| Not wiring providers into modules | Children won’t magically pick your alias and will default to the root’s default provider | In the parent, pass instances explicitly: providers = { aws = aws.dr }; keep children free of provider blocks | 
| Assuming data sources follow resource providers | Data can be read from the wrong region/account if not pinned | Set provider = aws.<alias>on data sources that must target a specific instance | 
| Misunderstanding assume_roleflow on aliases | The alias first uses base creds, then assumes the role — bad base creds cause STS failures | Ensure base creds can assume the target role; put assume_roleon the aliased provider with the correct region | 
| Module doesn’t declare configuration_aliases | Terraform 1.x requires modules that accept aliased providers to list them under configuration_aliases | Add configuration_aliases = [...]to the child’srequired_providersblock | 
A provider alias lets you define multiple configurations of the same Terraform provider (e.g., different regions/accounts).
Aliases do not replace Terraform workspaces. They are used to configure multiple providers in one run, while workspaces manage multiple state files.
Terraform is really powerful, but to achieve an end-to-end secure GitOps approach, you need to use a product that can run your Terraform workflows. Spacelift takes managing Terraform to the next level by giving you access to a powerful CI/CD workflow and unlocking features such as:
- Policies (based on Open Policy Agent)
- Multi-IaC workflows
- Self-service infrastructure
- Integrations with any third-party tools
If you want to learn more about Spacelift, create a free account today or book a demo with one of our engineers.
Note: New versions of Terraform are placed under the BUSL license, but everything created before version 1.5.x stays open-source. OpenTofu is an open-source version of Terraform that expands on Terraform’s existing concepts and offerings. It is a viable alternative to HashiCorp’s Terraform, being forked from Terraform version 1.5.6.
Manage Terraform better and faster
If you are struggling with Terraform automation and management, check out Spacelift. It helps you manage Terraform state, build more complex workflows, and adds several must-have capabilities for end-to-end infrastructure management.
