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

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

comments powered by Disqus

Copyright 2025. All rights reserved.