Iterating over providers in Opentofu
Overview
OpenTofu 1.9 delivers a long-awaited feature: provider iteration using for_each. This tutorial shows how to dynamically configure multiple provider instances—perfect for multi-region, multi-account, or multi-cloud workflows.
Why it matters
This feature brings OpenTofu closer to fulfilling a long-standing Terraform community request—something HashiCorp never implemented. It’s a game-changer for teams managing multi-region deployments, account isolation, or multi-cloud orchestration, all while keeping code DRY and maintainable.
If you’re working on modular DevOps scaffolds or multi-cloud pipelines, this unlocks a whole new level of flexibility.
Step 1: Define Your regions
variable "regions" {
type = set(string)
default = ["us-east-1", "us-west-2"]
}
Step 2: Iterate over providers
provider "aws" {
alias = "by_region"
for_each = var.regions
region = each.key
}
Each provider instance is now accessible via aws.by_region[“us-east-1”], etc.
Step 3: Use iterated providers in resources
resource "aws_vpc" "regional" {
for_each = var.regions
provider = aws.by_region[each.key]
cidr_block = "10.0.0.0/16"
tags = {
Name = "vpc-${each.key}"
}
}
This deploys one VPC per region using the correct provider instance.
Step 4: Use iterated providers in modules
module "regional_vpc" {
for_each = var.regions
source = "./modules/vpc"
providers = {
aws = aws.by_region[each.key]
}
region = each.key
}
Modules can’t accept provider maps directly, so you pass individual instances.
Tips and gotchas
- Always use aliases when iterating over providers.
- Avoid using the same expression for both provider and module for_each.
- Planning can slow down with large region/account sets—consider batching.
- Provider lifecycle is tied to the for_each expression—changing it can force re-creation.
Advanced pattern: Multi-account + multi-region
variable "accounts_regions" {
type = map(set(string))
default = {
dev = ["us-east-1", "us-west-2"]
prod = ["eu-central-1", "ap-southeast-2"]
}
}
provider "aws" {
alias = "by_account_region"
for_each = merge([
for account, regions in var.accounts_regions :
{ for region in regions : "${account}_${region}" => { account = account, region = region } }
]...)
region = each.value.region
assume_role {
role_arn = "arn:aws:iam::${each.value.account}:role/DeploymentRole"
}
}
This pattern lets you deploy across multiple accounts and regions with clean, dynamic provider instantiation.
Use Cases
- CI/CD pipelines targeting multiple environments
- Multi-cloud orchestration (e.g., AWS + Azure)
- Scalable SaaS deployments with regional isolation
- Reusable infrastructure modules with dynamic provider injection
