Passing Provider Configurations to Terraform Modules for Multi-Region Deployments

Terraform modules are the primary mechanism for reusing infrastructure code, and provider configuration is the most misunderstood aspect of module composition. Provider configurations are global to an entire Terraform configuration and can be shared across module boundaries. Provider configurations can be defined only in a root Terraform module. The way Terraform handles provider configuration in modules has evolved over time, and there are some important patterns you need to know to avoid errors and build maintainable infrastructure code.

By default, Terraform passes provider configurations from your root module down to any child modules you call. This implicit inheritance means you do not need to declare provider blocks inside modules unless you need custom configuration. For variables, explicitly pass them from the root to each module - there is no automatic sharing. Use required_providers in modules to document which providers they need, and use configuration aliases when a module needs multiple configurations of the same provider, like deploying to multiple AWS regions.

Implicit Provider Inheritance and Root Module Scope

Each resource in the configuration must be associated with one provider configuration. Provider configurations are used for all operations on associated resources, including destroying remote objects and refreshing state. Terraform retains, as part of its state, a reference to the provider configuration that was most recently used to apply changes to each resource.

A module intended to be called by one or more other modules must not contain any provider blocks. A module containing its own provider configurations is not compatible with the foreach, count, and dependson arguments that were introduced in Terraform v0.13.

Provider configurations can be passed down to descendant modules in two ways: either implicitly through inheritance, or explicitly via the providers argument within a module block. These two options are discussed in more detail in the following sections.

Implicit inheritance applies to the default, non-aliased provider. If the root defines a provider without an alias, child modules receive it automatically without any explicit passing. This is the recommended path for single-region, single-account deployments.

Implicit vs Explicit Provider Passing

Mechanism When It Applies Configuration Location Example Use Case
Implicit inheritance Default provider, no alias Root module provider block Single AWS region deployment
Explicit providers meta-argument Aliased providers or remapped providers Root module call to child Multi-region AWS deployment
required_providers documentation All modules Module root Declare provider version constraints

Explicit Provider Passing with the providers Meta-Argument

When you start building reusable Terraform modules, one of the first challenges you'll face is figuring out how providers and variables work across module boundaries. For variables, explicitly pass them from the root to each module - there is no automatic sharing. For providers, the default is inherited, but aliases must be passed explicitly.

The providers argument allows you to pass provider configurations from a calling module to a called module. The syntax is a map where keys are the provider names as referenced inside the child module, and values are the provider instances defined in the caller.

Passing provider configurations to modules is how you enable multi-region, multi-account, and multi-cloud Terraform deployments. Modules should declare their provider requirements with required_providers but never define provider blocks. The default non-aliased provider is inherited implicitly. Aliased providers must be passed explicitly using the providers argument.

A typical pattern for a root to module call is:

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

module "mymodule" {
source = "./my
module"
providers = {
aws = aws
}
}
```

Within the module, resources can be defined without an explicit provider argument and will use the passed configuration.

Multiple Provider Aliases in Modules

When using Terraform modules, you might need to work with resources in different regions or even different cloud providers. Terraform allows you to define multiple provider aliases within your modules to handle this scenario effectively.

Define Provider Aliases in the root module first. Each alias gives a provider instance a unique name.

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

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

Pass Provider Aliases to Module using the providers map. The keys must match the provider names used inside the module.

hcl module "my_module" { source = "./my_module" providers = { aws.east = aws.us-east-1 aws.west = aws.eu-west-1 } }

Use Provider Aliases within Module by referencing the specific provider in each resource.

hcl resource "aws_instance" "example" { provider = aws.east ami = "ami-0c55..." instance_type = "t3.micro" }

Key points for multiple aliases:

  • Use the alias argument to give each provider instance a unique name.
  • Pass providers to modules using a map within the providers block.
  • Reference provider aliases within the module using the dot notation, e.g., aws.east.
  • Ensure that the provider aliases used within the module match the keys defined in the providers block of the module call.

The same pattern applies when working with Terraform modules and utilizing different instances of the same provider, each configured with unique settings. This is where provider aliases come in handy.

Step 1 sets up two AWS providers with aliases west and east.

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

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

Step 2 passes these aliases to the my_module using the providers meta-argument.

hcl module "my_module" { source = "./my_module" providers = { aws.west = aws.west aws.east = aws.east } }

Step 3 accesses aliases within the module.

```hcl
resource "aws_instance" "example" {
provider = aws.west
}

resource "awss3bucket" "example" {
provider = aws.east
}
```

Inside the module, reference the passed aliases via the provider argument when defining resources to utilize the specific provider configuration. Ensure the keys in the module call match the provider names used within the module.

This method allows for greater flexibility and control over resource deployment by enabling the use of different provider settings within a single module. This approach is particularly beneficial when dealing with complex deployments that involve multiple regions, environments, or even different cloud providers.

requiredproviders, configurationaliases, and Module Contracts

Modules should declare their provider requirements with requiredproviders but never define provider blocks. The requiredproviders block documents which providers a module needs and pins compatible versions.

A module intended to be called by one or more other modules must not contain any provider blocks. A module containing its own provider configurations is not compatible with the foreach, count, and dependson arguments that were introduced in Terraform v0.13.

When a module needs multiple configurations of the same provider, declare configurationaliases in requiredproviders. This informs callers that the module expects multiple provider configurations.

The default non-aliased provider is inherited implicitly. Aliased providers must be passed explicitly using the providers argument. When nesting modules that need aliased providers or non-default mappings, make sure to pass those provider configurations through each level.

For nested module chains, provider passing must be propagated. If root calls module A, and module A calls module B, and module B needs an aliased provider, the root must pass the provider to module A, and module A must pass it on to module B using its own providers block.

hcl module "my_module" { source = "./modules/aws/my_module" providers = { aws.dev = aws.dev aws.stg = aws.stg aws.prod = aws.prod } }

Then in the module itself:

hcl module "set_iam_dev" { source = "./iam" providers = { aws.dev = aws.dev } }

Providers Within Modules - Configuration Language | Terraform | Use providers within Terraform modules. Learn about version constraints, aliases, implicit inheritance, and passing providers to Terraform modules.

Common Mistakes and Debugging Provider Issues

When a module is not using the provider you expect, check:

  • Does the module have a provider block inside it? That can prevent the caller's passed provider configuration from being used.
  • Are you passing the provider explicitly? If yes, is the key name correct?
  • Is the module nested? Providers might not be flowing through intermediate modules.

Use terraform providers to see which providers are configured:

bash terraform providers

This shows provider requirements for the entire configuration.

Common anti-patterns include defining provider blocks inside reusable modules. This prevents proper passing and breaks compatibility with foreach, count, and dependson. Another mistake is assuming variables are shared automatically. Variables are never inherited; they must be passed explicitly.

If you need different providers for different instances, you need separate module blocks rather than trying to conditionally swap providers inside a single resource.

Summary of Provider Passing Patterns

Providers are automatically inherited by child modules in Terraform, so you do not need to declare provider blocks inside modules unless you need custom configuration. For variables, explicitly pass them from the root to each module - there is no automatic sharing. Use required_providers in modules to document which providers they need, and use configuration aliases when a module needs multiple configurations of the same provider.

The recommended approaches for sharing providers and passing variables to modules, along with common mistakes to avoid, center on keeping provider definitions in the root, declaring requirements in modules, and using explicit providers maps for aliases.

Conclusion

Passing provider configurations to modules is the core mechanism that enables multi-region, multi-account, and multi-cloud Terraform deployments while keeping modules reusable and composable. Implicit inheritance handles the default provider cleanly, but aliased providers demand explicit passing through the providers meta-argument with exact key matching. Modules must remain free of provider blocks, declare their needs via requiredproviders and configurationaliases, and propagate provider mappings through nested levels when necessary. Debugging provider issues reduces to checking for stray provider blocks inside modules, verifying correct key names in providers maps, and ensuring propagation through intermediate modules. Following these patterns avoids state corruption, prevents provider mismatches, and produces infrastructure code that remains maintainable as complexity grows.

Sources

  1. devops-daily.com
  2. nulldog.com
  3. nulldog.com
  4. oneuptime.com
  5. developer.hashicorp.com

Related Posts