Multiple Providers in Terraform
Real-world infrastructure rarely lives in a single cloud provider, region, or account. Terraform handles this naturally — you can configure multiple providers in one configuration and even multiple instances of the same provider using aliases.
Multiple Different Providers
terraform { required_providers { aws = { source = "hashicorp/aws" version = "~> 5.50" } azurerm = { source = "hashicorp/azurerm" version = "~> 3.100" } cloudflare = { source = "cloudflare/cloudflare" version = "~> 4.30" } datadog = { source = "datadog/datadog" version = "~> 3.40" } }}
provider "aws" { region = "us-east-1"}
provider "azurerm" { features {} subscription_id = var.azure_subscription_id}
provider "cloudflare" { api_token = var.cloudflare_api_token}
provider "datadog" { api_key = var.datadog_api_key app_key = var.datadog_app_key}Using all four in one config:
# AWS: application infrastructureresource "aws_ecs_service" "api" { name = "api-service" cluster = aws_ecs_cluster.main.id desired_count = 3}
# Azure: analytics workloadresource "azurerm_databricks_workspace" "analytics" { name = "analytics-workspace" resource_group_name = azurerm_resource_group.data.name location = "East US" sku = "premium"}
# Cloudflare: DNS and CDNresource "cloudflare_record" "api" { zone_id = var.cloudflare_zone_id name = "api" value = aws_lb.main.dns_name type = "CNAME" proxied = true}
# Datadog: monitoringresource "datadog_monitor" "api_error_rate" { name = "API Error Rate" type = "metric alert" query = "sum(last_5m):sum:aws.applicationelb.httpcode_target_5xx{service:api}.as_rate() > 0.01" message = "@pagerduty-platform-on-call"}Multiple Instances: Provider Aliases
When you need the same provider type but configured differently (different regions, accounts, or credentials):
# Default provider — primary region (no alias needed)provider "aws" { region = "us-east-1"}
# Alias for disaster recovery regionprovider "aws" { alias = "dr" region = "us-west-2"}
# Alias for a different AWS accountprovider "aws" { alias = "production" region = "us-east-1" assume_role { role_arn = "arn:aws:iam::111122223333:role/TerraformRole" session_name = "terraform-production" }}
# Alias for shared services accountprovider "aws" { alias = "shared_services" region = "us-east-1" assume_role { role_arn = "arn:aws:iam::444455556666:role/TerraformRole" }}Using aliases in resources:
# Primary region — uses default provider (no provider argument needed)resource "aws_s3_bucket" "primary" { bucket = "mycompany-primary-data"}
# DR region — explicit provider referenceresource "aws_s3_bucket" "dr_replica" { provider = aws.dr # Uses us-west-2 provider bucket = "mycompany-dr-data"}
# Different accountresource "aws_cloudwatch_log_group" "app" { provider = aws.production name = "/app/api" retention_in_days = 90}Multi-Region Architecture Example
variable "regions" { type = list(string) default = ["us-east-1", "eu-west-1", "ap-southeast-1"]}
# Multi-region providersprovider "aws" { alias = "us_east" region = "us-east-1"}
provider "aws" { alias = "eu_west" region = "eu-west-1"}
provider "aws" { alias = "ap_southeast" region = "ap-southeast-1"}
# Route 53 health checks in each regionresource "aws_route53_health_check" "us" { provider = aws.us_east fqdn = "api.us.example.com" type = "HTTPS" port = 443 resource_path = "/health" failure_threshold = 3}
resource "aws_route53_health_check" "eu" { provider = aws.eu_west fqdn = "api.eu.example.com" type = "HTTPS" port = 443 resource_path = "/health" failure_threshold = 3}
# Global Route 53 latency routing (Route 53 is global, no region alias needed)resource "aws_route53_record" "api_us" { zone_id = var.hosted_zone_id name = "api.example.com" type = "A" set_identifier = "us-east-1" health_check_id = aws_route53_health_check.us.id
latency_routing_policy { region = "us-east-1" }
alias { name = aws_lb.us.dns_name zone_id = aws_lb.us.zone_id evaluate_target_health = true }}Passing Providers to Modules
When a module needs to use a specific provider alias:
# Root moduleprovider "aws" { alias = "replica" region = "eu-west-1"}
module "replica_bucket" { source = "./modules/s3-replica"
providers = { aws = aws.replica # Pass the aliased provider to the module }
bucket_name = "mycompany-eu-replica"}terraform { required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" configuration_aliases = [aws] # Declares this module expects an aliased aws provider } }}
resource "aws_s3_bucket" "replica" { # Uses the provider passed from root — no provider block needed here bucket = var.bucket_name}Cross-Cloud Data Flow Pattern
# Deploy to AWS, then use the output in Azureresource "aws_lb" "main" { name = "main-alb" load_balancer_type = "application" subnets = aws_subnet.public[*].id}
# Feed the AWS LB DNS name to Azure Traffic Managerresource "azurerm_traffic_manager_external_endpoint" "aws_origin" { name = "aws-origin" profile_id = azurerm_traffic_manager_profile.global.id target = aws_lb.main.dns_name weight = 50}This is how Terraform enables genuine multi-cloud architectures — resources from different providers can reference each other’s attributes directly in a single configuration.