Terraform

Terraform Provider Aliases: Multi-Account / Multi-Region Patterns and Exam Tips

2026-04-19
NicheeLab Editorial Team

When you operate across multiple cloud accounts or multiple regions at the same time, Terraform provider aliases are unavoidable. This article compactly covers design, modularization, and credential switching, all aligned with the official specification.

Content follows the official Terraform documentation (https://developer.hashicorp.com/terraform/language/providers), and version-specific behavior is treated carefully. The second half of the article covers the points most often tested on the exam, along with a sample question.

Provider Alias Basics

In Terraform, the alias meta-argument lets you have multiple configurations of the same provider (e.g., different regions or credentials). A configuration without an alias is the default configuration, while one with an alias becomes a separately named configuration. On the resource or data source side, you can explicitly select which configuration to use via the provider meta-argument.

Inside a module, the default provider configuration is implicitly inherited from the parent. To make the child use an aliased configuration, you pass it explicitly via the providers map on the module block. This is the core pattern for multi-account / multi-region operations.

  • alias is defined inside the provider block (e.g., provider "aws" { alias = "usw2" ... })
  • On the resource / data source side, select it with provider = aws.usw2
  • Pass it explicitly to a module as providers = { aws = aws.apne1, aws.usw2 = aws.usw2 }

Minimal alias example (using AWS)

terraform {
  required_providers {
    aws = { source = "hashicorp/aws", version = "~> 5.0" }
  }
}

provider "aws" {
  region = "ap-northeast-1" # default configuration
}

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

resource "aws_s3_bucket" "tokyo" {
  bucket = "example-tokyo-bucket-unique"
}

resource "aws_s3_bucket" "oregon" {
  provider = aws.usw2
  bucket   = "example-oregon-bucket-unique"
}

Multi-Account / Multi-Region Design Patterns

In practice, three patterns are common: 1) define multiple aliases in the root and use them directly, 2) modularize and inject multiple configurations via the providers map, or 3) split into separate stacks (directories) and separate the state as well. Choose based on scale and isolation requirements (compliance / blast radius).

When spanning multiple accounts (e.g., dev/prod) and multiple regions (e.g., ap-northeast-1 / us-west-2), it is safer to separate state along account boundaries. Region differences can be handled in the same state, but consider isolation based on business requirements once you account for change conflicts and concurrent runs.

  • Generally separate state by account; choose region separation based on requirements
  • Switch cross-account access explicitly via provider features like assume_role
  • Passing aliases at the module level improves testability and reusability
PatternScopeProsCaveats / Risks
Root-level (multiple aliases)Small to medium, single teamSimple; low learning curveTends toward a monolith; blast radius grows
Module + providers mapMedium to large, split teamsExplicit dependencies; easy to reuse and testEasy to forget passing providers and fall back to the implicit default
Split stacks (state separated too)Strict isolation (accounts / environments)Minimal blast radius; least-privilegeCross-cutting changes are harder; operations need extra care

Relationship between multi-account / multi-region and aliases

Root Moduleprovider "aws" × 4 (default / prod / dev / usw2)module "net-apne1"providers = { aws = aws, aws.usw2 = aws.usw2 }module "app-prod"providers = { aws = aws.prod }Account: SharedRegion: ap-northeast-1Account: ProdRegions: apne1 / usw2Multiple provider configurations in the Root Module, with account / region assignment via module.providers

Example: injecting multiple providers into a module

module "multi_region" {
  source = "./modules/multi_region"
  providers = {
    aws        = aws                 # default (ap-northeast-1)
    aws.usw2   = aws.usw2            # other region (us-west-2)
  }
}

Using Modules and the providers Map Correctly

A child module implicitly inherits the parent's default configuration, but aliased configurations are not implicitly inherited. When the child uses an alias, you must map the provider names the child references (e.g., aws, aws.usw2) to concrete parent instances (such as aws or aws.usw2) via the providers map on the module block.

The key is the name used inside the child module, and the value is the concrete instance defined in the parent. The value can be an expression, so conditional branching is possible, but you cannot create the alias itself dynamically (the provider block does not support for_each).

  • If a child module references an alias, always pass a matching entry via module.providers in the parent
  • If the default configuration is sufficient, the providers map is not needed
  • The provider argument can be computed, but the alias name itself must be defined statically

Example: using a separate region in a child module

# Parent side
module "net" {
  source = "./modules/net"
  providers = {
    aws       = aws        # default: ap-northeast-1
    aws.usw2  = aws.usw2   # additional: us-west-2
  }
}

# Child side (./modules/net/main.tf)
terraform {
  required_providers {
    aws = { source = "hashicorp/aws" }
  }
}

resource "aws_vpc" "primary" {
  cidr_block = "10.0.0.0/16"           # default configuration (ap-northeast-1)
}

resource "aws_subnet" "west" {
  provider   = aws.usw2                # aliased configuration (us-west-2)
  cidr_block = "10.1.0.0/24"
  vpc_id     = "vpc-xxxxxxxx"
}

Authentication and Privilege Separation (AWS example, generalizable)

Switch credentials and privileges per alias. With AWS there are multiple paths — profile, assume_role, environment variables — evaluated in a specific order of precedence. Following each provider's official documentation, it is safest to write only the intended method into your configuration.

When strong account-level isolation is required, separate not just aliases but also state per environment / account, and separate the execution role used from CI. You can switch regions via variables, but keeping alias names static makes migrations easier.

  • Give each alias its own credential configuration (e.g., profile or assume_role)
  • Avoid duplicating env vars and provider settings; prefer explicitness
  • Use least-privilege for cross-account operations; separating state is safer

Example: separate credentials per alias (AWS)

provider "aws" {
  alias   = "dev"
  region  = var.dev_region
  profile = "dev"
}

provider "aws" {
  alias  = "prod"
  region = var.prod_region
  assume_role {
    role_arn     = var.prod_role_arn
    session_name = "tf-prod"
  }
}

resource "aws_kms_key" "dev" {
  provider = aws.dev
  description = "dev key"
}

resource "aws_kms_key" "prod" {
  provider = aws.prod
  description = "prod key"
}

Specifying Aliases for State, Plan/Apply, and Import

State holds a reference to which provider configuration manages each resource. Be careful when changing alias names themselves — renaming alone, even with an identical API target, becomes a breeding ground for unexpected diffs. Choose alias names that will remain stable over the long term.

For import (the Terraform 1.5+ import block) and other migrations, you can also explicitly specify which alias configuration to use. plan/apply will use the appropriate configuration based on the provider meta-argument referenced by each resource.

  • As a rule, do not rename aliases (if necessary, plan a phased migration)
  • Specify provider on import to prevent wrong-region / wrong-account imports
  • Combine state separation with naming conventions to keep plan diffs from spreading

Specifying an alias in the import block (1.5+)

import {
  to       = aws_s3_bucket.oregon
  id       = "example-oregon-bucket-unique"
  provider = aws.usw2
}

Exam Tips and Gotchas

Both the Associate and Professional exams frequently ask about where aliases are defined, how to specify them on resources and modules, and the scope of implicit inheritance. In particular, forgetting to pass an alias to a module — causing the child to fall back to the default configuration and operate on an unintended region or account — is a common question pattern.

The provider argument can take an expression, but you cannot generate the alias itself dynamically. For sprawling region rollouts, supplement with directory splitting or templating.

  • alias is a meta-argument of the provider block; it is not part of required_providers
  • If a child module uses aws.usw2, you must populate the aws.usw2 key in the parent's module.providers
  • Data sources can also select an alias via the provider meta-argument
  • The provider block does not support count / for_each — you cannot mass-produce them in a loop
  • Workspaces are useful for switching variable values, but they are not a substitute for privilege / account isolation

A common mistake and the correct specification

# Incorrect (child uses aws.usw2 but parent does not pass it)
module "m" {
  source = "./m"
  # No providers map -> child sees only the default aws
}

# Correct
module "m" {
  source = "./m"
  providers = {
    aws.usw2 = aws.usw2
  }
}

Check Your Understanding

Associate / Pro

問題 1

A child module creates AWS resources in both us-west-2 and ap-northeast-1. Inside the child, some resources explicitly use aws.usw2. Which parent-module configuration is correct?

  1. module "child" { source = "./child" providers = { aws.usw2 = aws.usw2, aws = aws } }
  2. module "child" { source = "./child" } # Sufficient if the parent has a default aws
  3. module "child" { source = "./child" providers = { aws = aws.usw2 } }
  4. module "child" { source = "./child" providers = { aws.use2 = aws.usw2 } }

正解: A

If the child references aws.usw2, the parent's module.providers must map the key aws.usw2 to the parent's concrete aws.usw2 instance. The default aws alone will not resolve the child's aws.usw2 reference, and typos in the key name (such as aws.use2) are not acceptable either.

Frequently Asked Questions

Can I create provider aliases dynamically (e.g., with for_each or variables)?

No. The provider block does not support count or for_each. Define as many provider blocks as you need statically, and switch between them inside modules using the providers map.

Is the providers map required when a child module only uses the default configuration?

Not required. The parent's default configuration is inherited implicitly. You only need to pass the providers map explicitly when the child uses an aliased configuration.

What happens if I rename an alias later?

The provider configuration key referenced by state will change, which causes drift. Even if the underlying target is identical, unexpected side effects can occur, so the recommended policy is to avoid renaming. If you absolutely must rename, plan a phased migration and carefully review the plan diff and blast radius.

Check what you learned with practice questions

Practice with certification-focused question sets

無料で問題を解いてみる
Author

NicheeLab Editorial Team

NicheeLab editorial team focused on data engineering and cloud certification learning. Content is structured around practical study needs and official exam domains.


Related articles
Terraform

HCL Syntax: Terraform's Configuration Language (2026)

HCL2 fundamentals for Terraform — blocks, attributes, expres...

Terraform

Terraform Authoring & Operations Pro: Complete Guide (2026)

Tactics for the Terraform Pro exam — module authoring, works...

Terraform

Terraform Providers: Plugin Management Fundamentals (2026)

Provider mechanics — required_providers, versions, mirrors, ...

Terraform

Terraform Resource Blocks: Declarative Infra Units (2026)

Resource block fundamentals — addresses, references, common ...

Terraform

Terraform Data Sources: Read-Only External Data (2026)

Data source basics — declaration, refresh behavior, dependen...

Browse all Terraform articles (102)
© 2026 NicheeLab All rights reserved.