Terraform Provider Aliases: Multi-Region, Multi-Account Configuration Patterns

Terraform provider aliases allow the same provider to be configured more than once within a single run, each with its own credentials, region, endpoint, or assume role settings, and referenced per resource or per module call. The pattern replaces code duplication for cross-region and cross-account deployments and gives explicit control over which provider configuration backs every resource and data source.

The default provider configuration is the one defined in a provider block without an alias argument. Any resource that omits an explicit provider meta-argument falls back to that default. An alias is a named secondary configuration declared with alias = "<name>" inside a provider block. Resources and modules can select it with provider = aws.<alias> or by passing it through the providers meta-argument.

How Provider Aliases Work

A provider alias is a named secondary provider configuration that lets the same provider be used with different settings in one run. It is the mechanism for targeting multiple regions, accounts, or endpoints without duplicating code. Aliases improve clarity when managing cross-region or cross-account deployments.

Declaration is straightforward:

```
provider "aws" {
region = "eu-central-1"
}

provider "aws" {
alias = "use1"
region = "us-east-1"
}
```

The first block is the default. The second block is an alias named use1. A resource can reference it with:

resource "aws_s3_bucket" "example" { provider = aws.use1 bucket = "my-bucket-use1" }

Modules can also receive an aliased configuration:

module "example" { source = "./modules/vpc" providers = { aws = aws.use1 } }

The default configuration has no alias, so resources without an explicit provider use it automatically. When every provider block for a given provider type uses an alias, Terraform creates an implied empty default configuration. Any resource that does not specify a provider meta-argument then uses that empty default, which may lead to errors if required settings such as region are missing.

Default vs Aliased Provider Behavior

The default configuration targets the primary account and region. Aliased configurations target alternate accounts, regions, or endpoints.

```
provider "aws" {
region = "us-east-1"
}

provider "aws" {
alias = "west"
region = "us-west-2"
}
```

Resources that begin with aws_ use the default aws provider configuration unless you supply the provider argument.

```
resource "aws_instance" "foo" {
provider = aws.west
# …
}

resource "aws_instance" "bar" {
# uses default aws provider
}
```

If there are multiple aliases for a provider in your configuration, the provider block without an alias argument is the default configuration for that provider. If every provider block in your configuration uses an alias, Terraform creates an implied empty default configuration for that provider.

Implied Empty Default and Common Errors

The implied empty default is a frequent source of failures. When every provider block for a provider type uses an alias, Terraform creates an implied empty default configuration for that provider. Any resource that does not specify a provider meta-argument uses the empty default configuration.

Example:

```
provider "aws" {
alias = "east"
region = "us-east-1"
}

provider "aws" {
alias = "west"
region = "us-west-2"
}

resource "awss3bucket" "default_provider" {
bucket = "uses-implied-empty-config"
}
```

Terraform uses the implied empty default configuration for the aws_s3_bucket resource because the resource does not specify a provider argument. The result is an error due to missing region and credentials.

To avoid this, either keep a non-aliased default provider block or ensure every resource explicitly sets provider = aws.<alias>.

Multi-Region Deployments

When you deploy identical infrastructure to multiple regions, you do not want to copy-paste whole configurations. Aliased providers let you define one set of resources and select the region per instance.

A typical pattern:

```
provider "aws" {
alias = "eucentral"
region = "eu-central-1"
}

provider "aws" {
alias = "use1"
region = "us-east-1"
}

resource "awss3bucket" "eu" {
provider = aws.eucentral
bucket = "app-eu"
}

resource "awss3bucket" "us" {
provider = aws.use1
bucket = "app-us"
}
```

This keeps the resource definition identical while routing each instance to the correct provider configuration.

Cross-Account Access with Assume Role

Use different AWS credentials for different accounts by combining aliases with assume_role.

```
provider "aws" {
region = "us-east-1"
}

provider "aws" {
alias = "dev"
region = "us-east-1"
assumerole {
role
arn = "arn:aws:iam::123456789012:role/TerraformAdmin"
session_name = "terraform-dev"
}
}

provider "aws" {
alias = "shared"
region = "us-east-1"
assumerole {
role
arn = "arn:aws:iam::987654321098:role/TerraformAdmin"
session_name = "terraform-shared"
}
}
```

Resources then target the intended account:

```
resource "awsvpc" "production" {
cidr
block = "10.0.0.0/16"
tags = {
Name = "production-vpc"
Account = "production"
}
}

resource "awsvpc" "development" {
provider = aws.dev
cidr
block = "10.1.0.0/16"
tags = {
Name = "development-vpc"
Account = "development"
}
}

resource "awsecrrepository" "shared" {
provider = aws.shared
name = "shared-images"
imagescanningconfiguration {
scanonpush = true
}
}
```

Key operational notes:

  • Data sources follow resource providers. Data can be read from the wrong region or account if not pinned. Set provider = aws.<alias> on data sources that must target a specific instance.
  • The alias first uses base credentials, then assumes the role. Bad base credentials cause STS failures. Ensure base credentials can assume the target role and put assume_role on the aliased provider with the correct region.

Passing Providers to Modules

Modules can accept provider configurations through the providers argument.

Root module:

```
provider "aws" {
region = "us-east-1"
}

provider "aws" {
alias = "west"
region = "us-west-2"
}

module "vpceast" {
source = "./modules/vpc"
cidr
block = "10.0.0.0/16"
environment = "production"
}

module "vpcwest" {
source = "./modules/vpc"
providers = {
aws = aws.west
}
cidr
block = "10.1.0.0/16"
environment = "staging"
}
```

The child module must declare which aliases it can accept.

terraform { required_providers { aws = { source = "hashicorp/aws" version = ">= 2.7.0" configuration_aliases = [ aws.alternate ] } } }

The above requirements are identical to the previous, with the addition of the alias provider configuration name aws.alternate, which can be referenced by resources using the provider argument.

When a module needs two distinct provider configurations, the root can map names:

```
provider "aws" {
alias = "usw1"
region = "us-west-1"
}

provider "aws" {
alias = "usw2"
region = "us-west-2"
}

module "tunnel" {
source = "./tunnel"
providers = {
aws.src = aws.usw1
aws.dst = aws.usw2
}
}
```

The subdirectory ./tunnel must then declare the configuration aliases for the provider so the calling module can pass configurations with these names in its providers argument:

terraform { required_providers { aws = { source = "hashicorp/aws" version = ">= 2.7.0" configuration_aliases = [ aws.src, aws.dst ] } } }

Each resource should then have its own provider attribute set to either aws.src or aws.dst to choose which of the two provider configurations to use.

If you are writing a shared Terraform module, constrain only the minimum required provider version using a >= constraint.

Provider Alias Specification Table

| Attribute | Purpose |
| - Default provider block | No alias argument, serves as fallback for resources without explicit provider |
| - alias = "" | Names a secondary provider configuration for selection |
| - provider = aws. | Selects aliased configuration for a single resource or data source |
| - providers = { aws = aws. } | Passes aliased configuration to a child module |
| - configurationaliases | Declares which aliases a module can accept in requiredproviders |

Pitfalls and Best Practices

  • Sources follow resource providers. Always pin data sources to the same provider as the resources they support.
  • Module does not declare configuration_aliases. Terraform 1.x requires modules that accept aliased providers to list them under configuration_aliases. Add configuration_aliases = [...] to the child’s required_providers block.
  • Misunderstanding assume_role flow 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 and put assume_role on the aliased provider with the correct region.
  • Provider aliases do not replace Terraform workspaces. Aliases control provider configuration per resource, workspaces control state separation.

Historical Context

In Terraform v0.10 and earlier there was no explicit way to use different configurations of a provider in different modules in the same configuration, and so module authors commonly worked around this by writing provider blocks directly inside their modules, making the module have its own separate provider configurations separate from those declared in the root module. However, that pattern had a significant drawback: because a provider configuration is required to destroy the remote object associated with a resource instance as well as to create or update it, a provider configuration must always stay present in the overall Terraform configuration for longer than all of the resources it manages.

Conclusion

Provider aliases provide a precise, declarative way to reuse the same provider with different settings across regions, accounts, and endpoints within a single Terraform run. The default provider remains the implicit choice for resources without an explicit provider argument, while aliased providers enable targeted deployments through provider = aws.<alias> on resources and data sources and through the providers meta-argument for modules.

Correct use requires keeping a non-aliased default unless every resource is explicitly pinned, avoiding the implied empty default configuration. Modules that receive aliased providers must declare configuration_aliases in required_providers, and callers must map provider names explicitly. When combined with assume role and careful data source pinning, aliases deliver safe multi-account, multi-region infrastructure without code duplication, while preserving the lifecycle guarantees that provider configurations must outlive the resources they manage.

Sources

  1. Spacelift Terraform Provider Alias
  2. Hashicorp Terraform Provider Block
  3. Hashicorp Terraform Modules Providers
  4. OneUptime Terraform Provider Aliases

Related Posts