Scenario: Multi-Region Web App with AWS & Cloudflare
Use Case:
You’re deploying a highly available web application with a primary AWS region in us-east-1 and a disaster recovery region in eu-west-2. The app uses an S3 static website and a Cloudflare CDN for global caching.
Requirements to Implement
- Use
required_providers
to explicitly define AWS & Cloudflare providers. - Deploy an S3 bucket for static hosting in two AWS regions.
- Use
conditional expressions
to enable versioning only if it's the primary region. - Use
variables
for region selection, bucket name, and Cloudflare settings. - Use
built-in functions
(e.g.,lookup
,lower
,join
) for dynamic resource naming.
Terraform Implementation Plan
1. Define Required Providers & Multi-Region AWS Setup
- Configure AWS in us-east-1 (primary) & eu-west-2 (DR).
- Use Cloudflare for CDN.
2. Use Variables for Flexibility
- Allow users to enable multi-region via a Boolean flag.
- Store the Cloudflare zone ID dynamically.
3. Deploy S3 Buckets with Conditional Configurations
- Enable versioning only in the primary region.
- Use
count
andlookup()
to deploy the secondary region only if enabled.
4. Integrate with Cloudflare
- Create a Cloudflare DNS record dynamically.
Industry-standard Terraform structure for the multi-provider, multi-region deployment
✅ Modularized structure
✅ Terraform state management (S3 + DynamoDB)
✅ Dynamic backend configuration
✅ Secrets management with tfvars
✅ Optimized for reusability
File Structure
terraform-multi-region-app/
│── modules/
│ ├── s3/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── outputs.tf
│ ├── cloudflare/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── outputs.tf
│── environments/
│ ├── dev/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── terraform.tfvars
│ │ ├── backend.tf
│ ├── prod/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── terraform.tfvars
│ │ ├── backend.tf
│── global/
│ ├── providers.tf
│ ├── versions.tf
│── README.md
│── .gitignore
📌 1. Terraform Version & Providers (global/versions.tf)
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
cloudflare = {
source = "cloudflare/cloudflare"
version = "~> 4.0"
}
}
}
📌 2. Providers & Remote State (global/providers.tf)
provider "aws" {
region = var.primary_region
alias = "primary"
}
provider "aws" {
region = var.secondary_region
alias = "secondary"
}provider "cloudflare" {
api_token = var.cloudflare_api_token
}
📌 3. Remote State Backend (environments/dev/backend.tf)
terraform {
backend "s3" {
bucket = "terraform-state-bucket"
key = "multi-region/dev/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-lock"
encrypt = true
}
}
📌 4. S3 Bucket Module (modules/s3/main.tf)
resource "aws_s3_bucket" "this" {
provider = aws.primary
bucket = "${var.bucket_name}-${var.region}"
lifecycle {
prevent_destroy = true
}
}resource "aws_s3_bucket_versioning" "this" {
provider = aws.primary
bucket = aws_s3_bucket.this.id
versioning_configuration {
status = var.enable_versioning ? "Enabled" : "Suspended"
}
}
📌 Variables (modules/s3/variables.tf)
variable "bucket_name" {
type = string
description = "Base name for S3 bucket"
}
variable "region" {
type = string
description = "AWS region"
}variable "enable_versioning" {
type = bool
description = "Enable versioning for the S3 bucket"
default = true
}
📌 5. Cloudflare Module (modules/cloudflare/main.tf)
resource "cloudflare_record" "cdn" {
zone_id = data.cloudflare_zone.selected.id
name = var.record_name
value = var.s3_endpoint
type = "CNAME"
proxied = true
}
data "cloudflare_zone" "selected" {
name = var.cloudflare_zone
}
📌 Variables (modules/cloudflare/variables.tf)
variable "record_name" {
type = string
description = "Cloudflare DNS record name"
}
variable "s3_endpoint" {
type = string
description = "S3 website endpoint"
}variable "cloudflare_zone" {
type = string
description = "Cloudflare Zone Name"
}
📌 6. Environment-Specific Config (environments/dev/main.tf)
module "primary_s3" {
source = "../../modules/s3"
bucket_name = var.s3_bucket_name
region = var.primary_region
enable_versioning = true
}
module "secondary_s3" {
source = "../../modules/s3"
bucket_name = var.s3_bucket_name
region = var.secondary_region
enable_versioning = false
count = var.enable_multi_region ? 1 : 0
}module "cloudflare" {
source = "../../modules/cloudflare"
record_name = "app"
s3_endpoint = "s3-website.${var.primary_region}.amazonaws.com"
cloudflare_zone = var.cloudflare_zone
}
📌 Variables (environments/dev/variables.tf)
variable "primary_region" {
type = string
description = "Primary AWS Region"
default = "us-east-1"
}
variable "secondary_region" {
type = string
description = "Secondary AWS Region"
default = "eu-west-2"
}variable "enable_multi_region" {
type = bool
description = "Enable Multi-Region Disaster Recovery"
default = true
}variable "s3_bucket_name" {
type = string
description = "Base name for S3 bucket"
default = "my-multi-region-app"
}variable "cloudflare_zone" {
type = string
description = "Cloudflare Zone Name"
default = "example.com"
}
📌 7. Secrets Management (environments/dev/terraform.tfvars)
cloudflare_api_token = "your-cloudflare-token"
📌 8. Git Best Practices (.gitignore)
*.tfstate
*.tfvars
.terraform/
terraform.tfstate.backup
🚀 Industry-Standard Best Practices Applied
✅ Modular Structure: Separates S3 & Cloudflare into reusable modules
✅ Environment Segmentation: Different Terraform workspaces for dev/prod
✅ State Locking: Uses S3 backend with DynamoDB to prevent state corruption
✅ Security Best Practices: Secrets (tfvars
) are excluded from Git
✅ Scalability: Can easily extend to new AWS regions & services
📌 Next Steps
- Initialize Terraform:
cd environments/dev terraform init
2.Plan Deployment:
terraform plan
3.Apply Configuration:
terraform apply -auto-approve
Key Concepts Used in This Practice
✅ Multiple Providers: AWS (2 regions) & Cloudflare
✅ Multi-Region Deployment: Primary & DR (conditionally deployed)
✅ Required Providers Block: Ensures correct versions are used
✅ Variables & Conditional Expressions:
enable_multi_region ? 1 : 0
controls secondary bucket creationlookup()
andjoin()
functions used for dynamic naming
✅ Built-in Functions:join("-", [var.s3_bucket_name, var.primary_region])
→ Creates dynamic bucket nameslower(var.s3_bucket_name)
→ Ensures lowercase names