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
Last updated: August 2026

3.3 Configuration with Multiple Providers

Quick Answer: One root module can use many providers. Declare each in required_providers. Write one unaliased provider block as the default, then extra blocks with alias for other regions or accounts. Point a resource at an alias with provider = aws.west. Point a module at an alias with providers = { 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:

  1. Several provider plugins in one root module — hashicorp/aws plus hashicorp/random plus hashicorp/archive plus hashicorp/kubernetes, or AWS plus a third-party Datadog provider.
  2. Several configurations of the same plugin — two provider "aws" blocks, one for us-east-1 and one aliased for us-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

ScenarioWhat you writeWhat you do not write
Multi-region AWSTwo provider "aws" blocks; provider = aws.west on west-coast resourcesA made-up aws_west_instance type
AWS compute plus Datadog monitorshashicorp/aws and datadog/datadog in required_providersA single resource that creates an EC2 instance and a Datadog monitor together
AWS plus Kuberneteshashicorp/aws to build the cluster, hashicorp/kubernetes to manage workloadsAssuming aws_eks_cluster can create Deployments
AWS plus local utilitiesrandom / archive alongside awsTreating 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.west is 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 source off a third-party provider makes Terraform search hashicorp/<name>.
  • There is no official multi-cloud resource type that belongs to two vendors.
  • Aliasing every block does not remove the empty default configuration.
Test Your Knowledge

How do you make one aws_instance use an AWS provider configuration whose alias is west?

A
B
C
D
Test Your Knowledge

How do you pass an aliased AWS provider into a child module on Terraform 1.12?

A
B
C
D
Test Your Knowledge

Which configuration is a realistic Terraform Associate (004) multi-provider root module?

A
B
C
D