Terraform providers are plugins that enable Terraform to interact with cloud platforms, SaaS providers, and other APIs. Terraform sources providers from the Terraform registry by default, which hosts providers maintained by HashiCorp, our partners, and community members. Each provider supports a set of resource types and data sources that you can manage with Terraform.
To use Terraform to manage resources for your chosen cloud platform, you must first install the corresponding provider and configure authentication. With the provider installed, you can use Terraform to create and manage the resources it supports.
Provider configuration in Terraform modules is one of those areas where things can get confusing quickly. Modules inherit providers from their callers, but sometimes you need more control - deploying to multiple AWS accounts, managing resources across regions, or working with multiple provider instances. This post explains how to handle all of these scenarios cleanly.
Provider Fundamentals and Sourcing
Providers are the interface between Terraform core and external APIs. A provider configuration defines how Terraform authenticates and connects to a remote endpoint.
In this tutorial, you will learn how to source and version providers from the Terraform registry, configure and authenticate providers, and upgrade provider versions safely. You will also learn how to configure multiple instances of the same provider using aliases and control which providers your Terraform modules use to provision infrastructure.
This tutorial assumes that you are familiar with the Terraform workflow. If you are new to Terraform, complete the Get Started collection first.
You can complete this tutorial using AWS, Azure, or Google Cloud Platform.
Provider versioning is declared with a required_providers block inside a terraform block:
hcl
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 2.7.0"
}
}
}
A provider requirement says, for example, "This module requires version v2.7.0 of the provider hashicorp/aws and will refer to it as aws." It doesn't, however, specify any of the configuration settings that determine what remote endpoints the provider will access, such as an AWS region; configuration settings come from provider configurations, and a particular overall Terraform configuration can potentially have several different configurations for the same provider.
All provider blocks will use the same version of the given provider. Re-run terraform init to install the new module. Terraform installs each instance of a module in its own directory in your local workspace, since each instance of the module can be a different version.
$ terraform init
Initializing the backend...
Initializing modules...
Downloading registry.terraform.io/terraform-aws-modules/s3-bucket/aws 5.2.0 for website_east...
- website_east in .terraform/modules/website_east
Initializing provider plugins...
- Reusing previous version of hashicorp/aws from the dependency lock file
- Using previously-installed hashicorp/aws v6.6.0
Terraform has been successfully initialized!
Provider Configuration Scope and Module Boundaries
Each resource in the configuration must be associated with one provider configuration. Provider configurations, unlike most other concepts in Terraform, 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.
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.
If Terraform finds a resource instance tracked in the state whose provider configuration block is no longer available then it will return an error during planning, prompting you to reintroduce the provider configuration.
Although provider configurations are shared between modules, each module must declare its own provider requirements, so that Terraform can ensure that there is a single version of the provider that is compatible with all modules in the configuration and to specify the source address that serves as the global module-agnostic identifier for a provider.
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. For more information, see Legacy Shared Modules with Provider Configurations.
How Provider Inheritance Works
By default, a child module inherits the default provider from its parent. If you configure an AWS provider in your root module, every child module automatically uses it.
```hcl
Root module - providers.tf
provider "aws" {
region = "us-east-1"
}
This module automatically uses the aws provider from above
module "vpc" {
source = "./modules/vpc"
vpc_cidr = "10.0.0.0/16"
}
```
This works fine for simple setups.
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. 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.
The child module will automatically use the same default provider configuration as your root module.
Sometimes you need multiple configurations of the same provider in your workspace. For example, you might want to create resources in multiple regions or use different authentication credentials.
| Inheritance Mode | When Used | Provider Location |
|---|---|---|
| Implicit inheritance | Single provider, default configuration | Root module provider block |
| Explicit passing | Multiple aliases or non-default configs | Root module providers argument in module block |
| Required providers declaration | Module dependency documentation | Child module terraform { required_providers } |
Explicit Provider Passing and Aliases
Each resource in the configuration must be associated with one provider configuration. Provider configurations can be defined only in a root Terraform module.
Providers 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.
Modules use the providers argument to map provider aliases to their internal provider requirements. Resources, data sources, and modules without an explicit provider argument use the default non-aliased provider.
To declare multiple configuration names for a provider within a module, add the configuration_aliases argument:
hcl
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 2.7.0"
configuration_aliases = [...]
}
}
}
The TLDR: Providers are automatically inherited by child modules in Terraform, so you don't need to declare provider blocks inside modules unless you need custom configuration. For variables, explicitly pass them from the root to each module - there's 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.
A practical example of multiple provider instances:
```hcl
provider "aws" {
region = "us-east-1"
}
provider "aws" {
alias = "west"
region = "us-west-2"
}
module "website" {
source = "./modules/website"
providers = {
aws = aws.west
}
}
```
In this case the child module receives an explicit mapping and will not use the default us-east-1 configuration.
Provider Requirements and Variable Sharing
This guide covers the recommended approaches for sharing providers and passing variables to modules, along with common mistakes to avoid.
Providers are automatically inherited by child modules in Terraform, so you don't need to declare provider blocks inside modules unless you need custom configuration. For variables, explicitly pass them from the root to each module - there's no automatic sharing.
Each module must declare its own provider requirements. A provider requirement says for example "This module requires version v2.7.0 of the provider hashicorp/aws and will refer to it as aws."
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.
Common mistakes include placing provider blocks inside reusable modules, which breaks compatibility with foreach, count, and dependson introduced in Terraform v0.13. Another mistake is assuming variables are shared automatically; variables must be explicitly passed.
Dynamic Provider Configuration Patterns
Provider configuration in Terraform modules is one of those areas where things can get confusing quickly. Modules inherit providers from their callers, but sometimes you need more control - deploying to multiple AWS accounts, managing resources across regions, or working with multiple provider instances.
The recommended pattern is:
- Root module defines all provider configurations
- Child modules declare required_providers without provider blocks
- Module calls map specific provider aliases via providers argument when needed
Terraform retains, as part of its state, a reference to the provider configuration that was most recently used to apply changes to each resource. Removing a provider configuration that is still referenced in state will cause planning errors.
| Configuration Aspect | Root Module Responsibility | Child Module Responsibility |
|---|---|---|
| Provider block definition | Must define all provider configs | Must not define provider blocks |
| required_providers | Optional for top-level | Must declare requirements |
| Authentication | Set credentials | Inherit via inheritance |
| Alias mapping | Provide via providers argument | Consume via internal references |
Conclusion
Provider handling in Terraform modules is governed by a clear separation of concerns. Provider configurations are global and must live only in the root module, while each child module declares its provider requirements via requiredproviders. Implicit inheritance handles the common single-provider case, and explicit providers argument mapping enables advanced scenarios with multiple regions, accounts, or aliased configurations. The model prevents modules from being self-contained with provider blocks, which preserves composability with foreach, count, and depends_on. By keeping authentication and endpoint settings at the root and documenting dependencies inside modules, configurations remain portable, versioned, and safe to upgrade. Understanding the global nature of provider configurations, the state reference retention, and the two mechanisms for passing providers down the module tree is essential for building reliable, reusable infrastructure code at scale.