Stage 4 · Provision
Advanced Terraform Patterns
Multi-Cloud Terraform
Provider aliases, shared modules, and cross-cloud dependencies — managing infrastructure across multiple cloud providers.
Why Multi-Cloud?
Multi-cloud strategies arise from real requirements: GCP for data analytics, AWS for compute, Azure for Microsoft integrations, or avoiding vendor lock-in. Terraform's provider model makes multi-cloud infrastructure possible with a single tool.
Multi-cloud does not mean running the same workload everywhere. It means using the best provider for each use case while managing everything through a unified workflow.
Provider Aliases
Provider aliases let you use the same provider multiple times with different configurations. This is essential for multi-region or multi-account setups within a single cloud, and for multi-cloud configurations.
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.0"
}
google = {
source = "hashicorp/google"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "us-east-1"
}
provider "azurerm" {
features {}
subscription_id = var.azure_subscription_id
}
provider "google" {
project = var.gcp_project_id
region = "us-central1"
}Each provider block configures a different cloud. The aws, azurerm, and google providers can coexist. Resources from each provider are managed independently but can reference each other through data sources or outputs.
Cross-Cloud Modules
Cross-cloud modules abstract away provider specifics. A networking module might create VPCs in AWS or VNETs in Azure depending on the provider. This is done by accepting a provider variable or using conditional expressions.
provider "aws" {
alias = "us_east"
region = "us-east-1"
}
provider "aws" {
alias = "eu_west"
region = "eu-west-1"
}
module "us_cluster" {
providers = {
aws = aws.us_east
}
source = "./modules/eks"
name = "us-cluster"
}
module "eu_cluster" {
providers = {
aws = aws.eu_west
}
source = "./modules/eks"
name = "eu-cluster"
}The providers argument in a module block passes specific provider configurations to the module. Each module instance uses a different AWS region. The same pattern extends to multiple clouds.
Cross-Cloud DNS and Networking
A common multi-cloud pattern is DNS-based load balancing across clouds. You create resources in each cloud and register them in a shared DNS zone. Terraform manages the cross-cloud dependencies.
# AWS ALB
resource "aws_lb" "us" {
provider = aws.us_east
name = "us-alb"
load_balancer_type = "application"
subnets = module.us_vpc.public_subnet_ids
}
# Azure Load Balancer
resource "azurerm_lb" "eu" {
provider = azurerm
name = "eu-lb"
location = var.azure_location
resource_group_name = azurerm_resource_group.eu.name
}
# Cloudflare DNS with failover
resource "cloudflare_record" "api" {
zone_id = var.cloudflare_zone_id
name = "api"
type = "CNAME"
value = aws_lb.us.dns_name
failover_org_id = var.cloudflare_org_id
}
resource "cloudflare_record" "api_failover" {
zone_id = var.cloudflare_zone_id
name = "api"
type = "CNAME"
value = azurerm_lb.eu.fqdn
priority = 10
}This pattern uses Cloudflare for DNS-based failover across AWS and Azure. The primary record points to the US ALB. If US goes down, traffic fails over to the Azure LB. Terraform manages both clouds and the DNS configuration.
State per Cloud
Multi-cloud Terraform configurations can use a single state file or separate state files per cloud. Separate state files reduce blast radius — a state corruption in AWS does not affect Azure resources.
# Directory structure
infrastructure/
aws/
main.tf
backend.tf # State in s3://bucket/aws
azure/
main.tf
backend.tf # State in azurerm://container/azure
gcp/
main.tf
backend.tf # State in gcs://bucket/gcp
shared/
main.tf # Cross-cloud resources
backend.tf # State in s3://bucket/sharedEach cloud has its own backend and state file. Cross-cloud resources (like DNS records) live in a shared state. This separation means destroying an AWS environment does not accidentally remove Azure resources.
# In the AWS module
output "alb_dns" {
value = aws_lb.us.dns_name
}
# In the shared module
data "terraform_remote_state" "aws" {
backend = "s3"
config = {
bucket = "my-terraform-state"
key = "aws/terraform.tfstate"
region = "us-east-1"
}
}
resource "cloudflare_record" "api" {
value = data.terraform_remote_state.aws.outputs.alb_dns
}The terraform_remote_state data source reads outputs from another state file. This lets the shared module reference AWS resources without coupling the state files. Changes to AWS do not require changes to the shared module.
Best Practices
- Separate state files per cloud to reduce blast radius.
- Use provider aliases to pass specific configurations to modules.
- Keep cross-cloud resources in a dedicated module or state file.
- Use terraform_remote_state to share outputs between states.
- Test each cloud independently before testing cross-cloud dependencies.
- Document which cloud manages which resources — multi-cloud is complex.
Every additional cloud provider adds provider-specific configuration, credential management, networking complexity, and testing burden. Only go multi-cloud when there is a real business requirement.
Do not force abstractions for the sake of it. If 90% of your infrastructure is on AWS, use AWS-native modules. Abstract only the cross-cloud parts that genuinely need portability.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.