Cloud  /  Terraform

IaC Terraform 50 guides · updated 2026

Infrastructure as code done right — providers, state, reusable modules, and the workflow patterns that keep multi-cloud deployments sane in 2026.

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

versions.tf
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 infrastructure
resource "aws_ecs_service" "api" {
name = "api-service"
cluster = aws_ecs_cluster.main.id
desired_count = 3
}
# Azure: analytics workload
resource "azurerm_databricks_workspace" "analytics" {
name = "analytics-workspace"
resource_group_name = azurerm_resource_group.data.name
location = "East US"
sku = "premium"
}
# Cloudflare: DNS and CDN
resource "cloudflare_record" "api" {
zone_id = var.cloudflare_zone_id
name = "api"
value = aws_lb.main.dns_name
type = "CNAME"
proxied = true
}
# Datadog: monitoring
resource "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 region
provider "aws" {
alias = "dr"
region = "us-west-2"
}
# Alias for a different AWS account
provider "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 account
provider "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 reference
resource "aws_s3_bucket" "dr_replica" {
provider = aws.dr # Uses us-west-2 provider
bucket = "mycompany-dr-data"
}
# Different account
resource "aws_cloudwatch_log_group" "app" {
provider = aws.production
name = "/app/api"
retention_in_days = 90
}

Multi-Region Architecture Example

variables.tf
variable "regions" {
type = list(string)
default = ["us-east-1", "eu-west-1", "ap-southeast-1"]
}
# Multi-region providers
provider "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 region
resource "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 module
provider "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"
}
modules/s3-replica/main.tf
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 Azure
resource "aws_lb" "main" {
name = "main-alb"
load_balancer_type = "application"
subnets = aws_subnet.public[*].id
}
# Feed the AWS LB DNS name to Azure Traffic Manager
resource "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.