3.3 Configuration with Multiple Providers
Key Takeaways
- Multiple configurations of the same provider use alias; attach one to a resource with provider = aws.west
- Pass configurations into a child module with providers = { aws = aws.west }; aliased configurations are not inherited automatically
- A single root module can require several providers at once — aws, random, archive, kubernetes — each with its own required_providers entry
- Third-party providers such as datadog/datadog need an explicit source; there is no resource type that belongs to two cloud vendors at once
- If every provider block is aliased, Terraform still implies an empty default, and resources without provider = use that empty default
3.3 Configuration with Multiple Providers
Quick Answer: One root module can use many providers. Declare each in
required_providers. Write one unaliasedproviderblock as the default, then extra blocks withaliasfor other regions or accounts. Point a resource at an alias withprovider = aws.west. Point a module at an alias withproviders = { aws = aws.west }.
Objective 2c on Terraform Associate (004) asks you to write configuration that uses multiple providers. "Multiple" means two different things, and the exam mixes them:
- Several provider plugins in one root module —
hashicorp/awsplushashicorp/randomplushashicorp/archiveplushashicorp/kubernetes, or AWS plus a third-party Datadog provider. - Several configurations of the same plugin — two
provider "aws"blocks, one forus-east-1and one aliased forus-west-2.
Both are ordinary Terraform 1.12. Neither requires a special multi-cloud resource type.
Several plugins in one root module
Each plugin needs its own required_providers entry. Utility providers count. This is a realistic 004 root module:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
archive = {
source = "hashicorp/archive"
version = "~> 2.4"
}
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 2.32"
}
datadog = {
source = "datadog/datadog"
version = "~> 3.0"
}
}
}
provider "aws" {
region = "us-east-1"
}
provider "kubernetes" {
config_path = "~/.kube/config"
}
provider "datadog" {
api_url = "https://api.datadoghq.com/"
# api_key and app_key come from DD_API_KEY / DD_APP_KEY
}
resource "random_id" "suffix" {
byte_length = 4
}
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
}
resource "datadog_monitor" "cpu" {
name = "web CPU ${random_id.suffix.hex}"
type = "metric alert"
query = "avg(last_5m):avg:system.cpu.user{host:web} > 80"
message = "CPU is high"
}
random and archive often need no provider block because they have no required arguments. datadog must set source = "datadog/datadog"; if you omit source, Terraform looks for hashicorp/datadog and terraform init fails.
There is no resource type that is half AWS and half Azure. Multi-cloud means aws_instance from hashicorp/aws next to azurerm_linux_virtual_machine from hashicorp/azurerm, each talking to its own API. Do not invent aws_azure_hybrid_instance or a fictional hashicorp/multicloud wrapper on 004.
Several configurations of the same provider
Use multiple provider blocks with the same local name. Give every extra block a unique alias:
provider "aws" {
region = "us-east-1"
}
provider "aws" {
alias = "west"
region = "us-west-2"
}
The block without alias is the default. Resources whose type starts with aws_ use us-east-1 unless you override them. Refer to an alias as <local-name>.<alias> — here aws.west.
Attach an alias to a resource or data source
Use the provider meta-argument (singular) on the resource:
resource "aws_instance" "east" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
# default aws provider — us-east-1
}
resource "aws_instance" "west" {
provider = aws.west
ami = "ami-0abcdef1234567890"
instance_type = "t3.micro"
}
data "aws_ami" "west_al2023" {
provider = aws.west
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-*-x86_64"]
}
}
provider = aws.west is a reference, not a string. Quotes around aws.west are wrong. Putting alias = "west" inside the resource block is also wrong — alias belongs on the provider block only.
A common 004 picture is active/standby infrastructure: an aws_s3_bucket in the default region and a replica bucket with provider = aws.west. Same plugin, two configurations, two remote accounts or regions.
The empty-default trap
If every provider "aws" block has an alias, Terraform still creates an implied empty default. Any aws_* resource that forgets provider = ... uses that empty default. If the AWS provider cannot discover a region, plan fails. Prefer leaving one unaliased block as a real default, or put provider = on every resource when you alias everything.
Passing providers into modules
Child modules inherit default (unaliased) provider configurations from the caller automatically. They do not inherit aliases. To hand an alias to a module, use the providers meta-argument (plural) on the module block. The value is a map: keys are names the child expects, values are configurations from the parent.
provider "aws" {
region = "us-east-1"
}
provider "aws" {
alias = "west"
region = "us-west-2"
}
module "vpc_west" {
source = "./modules/vpc"
providers = {
aws = aws.west
}
}
Inside ./modules/vpc, resources still say resource "aws_vpc" "this" with no meta-argument. Their default aws is remapped to the parent's aws.west, so the VPC lands in us-west-2. Specifying providers cancels the usual inheritance: the child sees only the configurations you listed.
When a reusable module itself needs two configurations of the same provider (a tunnel with a source and destination region), the child declares configuration_aliases and the caller maps each name:
# inside the child module
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.0"
configuration_aliases = [aws.src, aws.dst]
}
}
}
# at the call site
module "tunnel" {
source = "./modules/tunnel"
providers = {
aws.src = aws.east
aws.dst = aws.west
}
}
HashiCorp recommends defining provider blocks in the root module and passing them down. Do not hide extra provider blocks inside child modules if you can avoid it — callers cannot see which region those nested blocks target. Child modules still declare their own required_providers (source and version). They do not inherit those requirements from the parent.
Third-party providers and exam scenarios
| Scenario | What you write | What you do not write |
|---|---|---|
| Multi-region AWS | Two provider "aws" blocks; provider = aws.west on west-coast resources | A made-up aws_west_instance type |
| AWS compute plus Datadog monitors | hashicorp/aws and datadog/datadog in required_providers | A single resource that creates an EC2 instance and a Datadog monitor together |
| AWS plus Kubernetes | hashicorp/aws to build the cluster, hashicorp/kubernetes to manage workloads | Assuming aws_eks_cluster can create Deployments |
| AWS plus local utilities | random / archive alongside aws | Treating random_id as an AWS resource |
Datadog, Cloudflare, PagerDuty, and similar partner providers follow the same rules as HashiCorp providers: explicit source, a version constraint, terraform init, and a provider block for their API keys (preferably from the environment).
004 traps for objective 2c
provider = aws.westis for resources and data sources.providers = { aws = aws.west }is for modules. Do not swap them.- Aliases are never inherited by child modules unless you pass
providers. - Leaving
sourceoff a third-party provider makes Terraform searchhashicorp/<name>. - There is no official multi-cloud resource type that belongs to two vendors.
- Aliasing every block does not remove the empty default configuration.
How do you make one aws_instance use an AWS provider configuration whose alias is west?
How do you pass an aliased AWS provider into a child module on Terraform 1.12?
Which configuration is a realistic Terraform Associate (004) multi-provider root module?