What is Multi Provider? #

Most Terraform tutorials start with a single provider — usually AWS or GCP. But real infrastructure is rarely that simple. You might need to create resources in two AWS regions at once for disaster recovery, manage DNS on Cloudflare while the servers are on AWS, or create a Kubernetes cluster on GCP while storing secrets in HashiCorp Vault. Multi-provider is Terraform’s ability to manage all of this in a single configuration, with proper coordination between resources from different sources.

flowchart TD
    A["Terraform\nConfig"] --> B["AWS\nap-southeast-1"]
    A --> C["AWS\nus-east-1"]
    A --> D["Cloudflare"]
    A --> E["Vault"]

    B --> F["EC2, RDS, S3"]
    C --> G["DR Resources"]
    D --> H["DNS Records"]
    E --> I["Secrets"]

    style A fill:#3b82f6,stroke:#1e40af,color:#fff
    style B fill:#f59e0b,stroke:#d97706,color:#fff
    style C fill:#f59e0b,stroke:#d97706,color:#fff
    style D fill:#ef4444,stroke:#dc2626,color:#fff
    style E fill:#8b5cf6,stroke:#6d28d9,color:#fff

Basic Provider Configuration #

By default, one provider block is enough for a single deployment. The default provider doesn’t need an alias.

# Single provider — the most common
provider "aws" {
  region = "ap-southeast-1"
}

# All resources in .tf files use this provider automatically
resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"
}

Provider Aliases: The Foundation of Multi-Provider #

An alias is a mechanism that gives an extra name to a provider configuration. With aliases, you can have multiple instances of the same provider — for example, AWS in two different regions — or several different providers in one configuration.

# AWS provider for the primary region (no alias = the default provider)
provider "aws" {
  region = "ap-southeast-1"
}

# AWS provider for the disaster recovery region (with an alias)
provider "aws" {
  alias  = "us_east"
  region = "us-east-1"
}

# A provider for DNS (a completely different provider)
provider "cloudflare" {
  api_token = var.cloudflare_api_token
}
# A resource using the default provider (no need to mention the provider)
resource "aws_vpc" "primary" {
  provider   = aws       # Optional — this is the default
  cidr_block = "10.0.0.0/16"
}

# A resource using a provider with an alias
resource "aws_vpc" "dr" {
  provider   = aws.us_east  # Format: <type>.<alias>
  cidr_block = "10.1.0.0/16"
}

# A resource from a different provider
resource "cloudflare_record" "app" {
  zone_id = var.cloudflare_zone_id
  name    = "app"
  value   = aws_lb.main.dns_name
  type    = "CNAME"
}

Common Multi-Provider Use Cases #

1. MULTI-REGION (AWS)
   Reason: Disaster recovery, latency reduction, data residency
   Pattern: A provider alias per region

2. MULTI-ACCOUNT (AWS)
   Reason: Environment isolation, billing separation
   Pattern: A provider alias per account with different assume_role

3. MULTI-CLOUD
   Reason: Best-of-breed services, vendor lock-in mitigation
   Pattern: Different providers (aws + google + azurerm)

4. CLOUD + SaaS
   Reason: DNS on Cloudflare, monitoring on Datadog, secrets in Vault
   Pattern: A cloud provider + SaaS/tool providers

5. KUBERNETES
   Reason: Deploying to k8s while provisioning its infrastructure
   Pattern: The aws/gcp provider + kubernetes/helm providers

Multi-Region: Disaster Recovery #

One of the most common multi-provider use cases is replicating resources to a second region for disaster recovery.

provider "aws" {
  region = "ap-southeast-1"  # Primary region — Singapore
}

provider "aws" {
  alias  = "dr"
  region = "ap-northeast-1"  # DR region — Tokyo
}

# Primary database in Singapore
resource "aws_db_instance" "primary" {
  identifier     = "production-db-primary"
  engine         = "postgres"
  instance_class = "db.r5.large"
  # ...

  backup_retention_period = 7
}

# Read replica in Tokyo for DR
resource "aws_db_instance" "replica" {
  provider = aws.dr  # This resource is created in the Tokyo region

  identifier          = "production-db-replica"
  replicate_source_db = aws_db_instance.primary.arn
  instance_class      = "db.r5.large"
}

# S3 bucket in Tokyo for backup storage
resource "aws_s3_bucket" "dr_backup" {
  provider = aws.dr

  bucket = "production-dr-backup-ap-northeast-1"
}

Multi-Account: Environment Isolation #

With multi-account, every environment lives in a separate AWS account. The provider alias determines which account a resource is created in.

# Provider for the production account
provider "aws" {
  alias  = "production"
  region = "ap-southeast-1"

  assume_role {
    role_arn = "arn:aws:iam::333333333:role/TerraformDeployRole"
  }
}

# Provider for the shared services account (DNS, artifact registry, etc.)
provider "aws" {
  alias  = "shared"
  region = "ap-southeast-1"

  assume_role {
    role_arn = "arn:aws:iam::444444444:role/TerraformReadRole"
  }
}

# Resource in the production account
resource "aws_instance" "app" {
  provider      = aws.production
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.medium"
}

# Read the hosted zone from the shared account
data "aws_route53_zone" "main" {
  provider = aws.shared
  name     = "myapp.com"
}

# Create a DNS record in the shared account pointing at a production resource
resource "aws_route53_record" "app" {
  provider = aws.shared

  zone_id = data.aws_route53_zone.main.zone_id
  name    = "app.myapp.com"
  type    = "A"

  alias {
    name                   = aws_lb.main.dns_name
    zone_id                = aws_lb.main.zone_id
    evaluate_target_health = true
  }
}

Multi-Cloud: AWS + Cloudflare #

provider "aws" {
  region = "ap-southeast-1"
}

provider "cloudflare" {
  api_token = var.cloudflare_api_token
}

# Infrastructure on AWS
resource "aws_lb" "main" {
  name               = "production-lb"
  internal           = false
  load_balancer_type = "application"
  subnets            = module.vpc.public_subnet_ids
}

# DNS on Cloudflare pointing at the AWS load balancer
resource "cloudflare_record" "app" {
  zone_id = var.cloudflare_zone_id
  name    = "app"
  value   = aws_lb.main.dns_name  # Reference to an AWS resource
  type    = "CNAME"
  proxied = true  # Enable the Cloudflare proxy (WAF, DDoS protection)
  ttl     = 1     # Auto TTL when proxied
}
flowchart LR
    A["AWS LB"] -->|"dns_name"| B["Cloudflare\nCNAME Record"]
    C["AWS RDS\nPrimary"] -->|"arn"| D["AWS RDS\nReplica"]

    style A fill:#f59e0b,stroke:#d97706,color:#fff
    style B fill:#ef4444,stroke:#dc2626,color:#fff
    style C fill:#10b981,stroke:#059669,color:#fff
    style D fill:#10b981,stroke:#059669,color:#fff

Providers Inside Modules #

When a module needs a specific provider (not the default), there are two ways to pass it: implicitly (the module inherits the default provider) or explicitly (the root module passes a provider alias to the module).

# The explicit way: the root module passes provider aliases to a child module

# modules/replication/main.tf
terraform {
  required_providers {
    aws = {
      source                = "hashicorp/aws"
      version               = ">= 5.0"
      configuration_aliases = [aws.primary, aws.replica]
      # This module needs TWO aws configurations: primary and replica
    }
  }
}

# The root module passes provider aliases to the module
module "replication" {
  source = "./modules/replication"

  providers = {
    aws.primary = aws           # The default provider becomes aws.primary in the module
    aws.replica = aws.us_east   # The alias provider becomes aws.replica in the module
  }
}


Provider Aliasing for Multi-Region #

Aliasing lets one configuration use several instances of the same provider with different configurations.

# The default provider
provider "aws" {
  region = "ap-southeast-1"
}

# A provider alias for another region
provider "aws" {
  alias  = "us_east"
  region = "us-east-1"
}

# A provider alias for another account
provider "aws" {
  alias   = "shared_services"
  region  = "ap-southeast-1"
  assume_role {
    role_arn = "arn:aws:iam::999888777666:role/CrossAccountRole"
  }
}

# Using a specific provider
resource "aws_s3_bucket" "logs" {
  provider = aws.us_east
  bucket   = "my-logs-us-east"
}

resource "aws_s3_bucket" "shared" {
  provider = aws.shared_services
  bucket   = "shared-artifacts"
}
flowchart TD
    A["Root Config"] --> B["aws (default)\nap-southeast-1"]
    A --> C["aws.us_east\nus-east-1"]
    A --> D["aws.shared_services\n999888777666"]

    B --> E["Primary\nresources"]
    C --> F["Cross-region\nreplication"]
    D --> G["Shared\nservices"]

    style B fill:#e8f5e9,stroke:#2e7d32
    style C fill:#e3f2fd,stroke:#1565c0
    style D fill:#fff3e0,stroke:#e65100

Provider Version Constraints #

Each provider has a version that should be locked for consistency.

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
    cloudflare = {
      source  = "cloudflare/cloudflare"
      version = "~> 4.0"
    }
    datadog = {
      source  = "datadog/datadog-aws"
      version = "~> 3.0"
    }
  }
  required_version = ">= 1.6.0"
}
# Lock file for cross-platform consistency
# .terraform.lock.hcl is committed to Git
terraform providers lock   -platform=linux_amd64   -platform=darwin_arm64   -platform=windows_amd64

# Update the provider to the latest version
terraform init -upgrade
# Review the changes in .terraform.lock.hcl before committing

Multi-Provider Use Cases #

COMMON MULTI-PROVIDER SCENARIOS:

1. AWS + Cloudflare: DNS management
   - AWS: compute, storage, networking
   - Cloudflare: DNS records, CDN, WAF

2. AWS + Datadog: monitoring
   - AWS: infrastructure
   - Datadog: dashboards, alerts, APM

3. AWS + PagerDuty: incident management
   - AWS: cloud resources
   - PagerDuty: escalation policies, schedules

4. AWS + GitHub: CI/CD + infrastructure
   - AWS: cloud resources
   - GitHub: repositories, teams, webhooks

5. AWS + Kubernetes: container orchestration
   - AWS: EKS cluster, networking
   - Kubernetes: deployments, services, ingress

Provider Inheritance #

# Provider configuration in the root module
# Child modules automatically inherit the provider from the root

# Root module:
provider "aws" {
  region = "ap-southeast-1"
  
  default_tags {
    tags = {
      Project   = "my-project"
      ManagedBy = "terraform"
    }
  }
}

# The child module automatically uses the provider above
# NO provider configuration needed in the child module

# For alias providers, you must pass them explicitly:
module "us_east_resources" {
  source = "./modules/regional"
  providers = {
    aws = aws.us_east
  }
}

Summary #

  • Provider aliases are the basic mechanism of multi-provider — giving extra names to provider configurations so resources can use them explicitly.
  • Alias usage format: provider = <type>.<alias> inside a resource block.
  • A provider without an alias is the default — resources that don’t mention provider explicitly use the default provider.
  • Four main use cases: multi-region (DR), multi-account (isolation), multi-cloud, and cloud + SaaS combinations (Cloudflare, Datadog, Vault).
  • Resources from different providers can reference each othercloudflare_record.app.value = aws_lb.main.dns_name is a normal and valid pattern.
  • Modules needing specific provider aliases must define configuration_aliases in required_providers and receive the provider via a providers block when called.

← Previous: Directory Based   Next: Cross Provider Reference →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact