Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Comprehensive Cloudflare platform skill covering Workers, D1, R2, KV, AI, Durable Objects, and security.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/terraform/patterns.md
1# Terraform Patterns & Use Cases23Architecture patterns, multi-environment setups, and real-world use cases.45## Recommended Directory Structure67```8terraform/9├── environments/10│ ├── production/11│ │ ├── main.tf12│ │ └── terraform.tfvars13│ └── staging/14│ ├── main.tf15│ └── terraform.tfvars16├── modules/17│ ├── zone/18│ ├── worker/19│ └── dns/20└── shared/ # Shared resources across envs21└── main.tf22```2324**Note:** Cloudflare recommends avoiding modules for provider resources due to v5 auto-generation complexity. Prefer environment directories + shared state instead.2526## Multi-Environment Setup2728```hcl29# Directory: environments/{production,staging}/main.tf + modules/{zone,worker,pages}30module "zone" {31source = "../../modules/zone"; account_id = var.account_id; zone_name = "example.com"; environment = "production"32}33module "api_worker" {34source = "../../modules/worker"; account_id = var.account_id; zone_id = module.zone.zone_id35name = "api-worker-prod"; script = file("../../workers/api.js"); environment = "production"36}37```3839## R2 State Backend4041```hcl42terraform {43backend "s3" {44bucket = "terraform-state"45key = "cloudflare.tfstate"46region = "auto"47endpoints = { s3 = "https://<account_id>.r2.cloudflarestorage.com" }48skip_credentials_validation = true49skip_region_validation = true50skip_requesting_account_id = true51skip_metadata_api_check = true52skip_s3_checksum = true53}54}55```5657## Worker with All Bindings5859```hcl60locals { worker_name = "full-stack-worker" }61resource "cloudflare_workers_kv_namespace" "app" { account_id = var.account_id; title = "${local.worker_name}-kv" }62resource "cloudflare_r2_bucket" "app" { account_id = var.account_id; name = "${local.worker_name}-bucket" }63resource "cloudflare_d1_database" "app" { account_id = var.account_id; name = "${local.worker_name}-db" }6465resource "cloudflare_worker_script" "app" {66account_id = var.account_id; name = local.worker_name; content = file("worker.js"); module = true67compatibility_date = "2025-01-01"68kv_namespace_binding { name = "KV"; namespace_id = cloudflare_workers_kv_namespace.app.id }69r2_bucket_binding { name = "BUCKET"; bucket_name = cloudflare_r2_bucket.app.name }70d1_database_binding { name = "DB"; database_id = cloudflare_d1_database.app.id }71secret_text_binding { name = "API_KEY"; text = var.api_key }72}73```7475## Wrangler Integration7677**CRITICAL**: Wrangler and Terraform must NOT manage same resources.7879**Terraform**: Zones, DNS, security rules, Access, load balancers, worker deployments (CI/CD), KV/R2/D1 resource creation80**Wrangler**: Local dev (`wrangler dev`), manual deploys, D1 migrations, KV bulk ops, log streaming (`wrangler tail`)8182### CI/CD Pattern8384```hcl85# Terraform creates infrastructure86resource "cloudflare_workers_kv_namespace" "app" { account_id = var.account_id; title = "app-kv" }87resource "cloudflare_d1_database" "app" { account_id = var.account_id; name = "app-db" }88output "kv_namespace_id" { value = cloudflare_workers_kv_namespace.app.id }89output "d1_database_id" { value = cloudflare_d1_database.app.id }90```9192```yaml93# GitHub Actions: terraform apply → envsubst wrangler.jsonc.template → wrangler deploy94- run: terraform apply -auto-approve95- run: |96export KV_NAMESPACE_ID=$(terraform output -raw kv_namespace_id)97envsubst < wrangler.jsonc.template > wrangler.jsonc98- run: wrangler deploy99```100101## Use Cases102103### Static Site + API Worker104105```hcl106resource "cloudflare_pages_project" "frontend" {107account_id = var.account_id; name = "frontend"; production_branch = "main"108build_config { build_command = "npm run build"; destination_dir = "dist" }109}110resource "cloudflare_worker_script" "api" {111account_id = var.account_id; name = "api"; content = file("api-worker.js")112d1_database_binding { name = "DB"; database_id = cloudflare_d1_database.api_db.id }113}114resource "cloudflare_dns_record" "frontend" {115zone_id = cloudflare_zone.main.id; name = "app"; content = cloudflare_pages_project.frontend.subdomain; type = "CNAME"; proxied = true116}117resource "cloudflare_worker_route" "api" {118zone_id = cloudflare_zone.main.id; pattern = "api.example.com/*"; script_name = cloudflare_worker_script.api.name119}120```121122### Multi-Region Load Balancing123124```hcl125resource "cloudflare_load_balancer_pool" "us" {126account_id = var.account_id; name = "us-pool"; monitor = cloudflare_load_balancer_monitor.http.id127origins { name = "us-east"; address = var.us_east_ip }128}129resource "cloudflare_load_balancer_pool" "eu" {130account_id = var.account_id; name = "eu-pool"; monitor = cloudflare_load_balancer_monitor.http.id131origins { name = "eu-west"; address = var.eu_west_ip }132}133resource "cloudflare_load_balancer" "global" {134zone_id = cloudflare_zone.main.id; name = "api.example.com"; steering_policy = "geo"135default_pool_ids = [cloudflare_load_balancer_pool.us.id]136region_pools { region = "WNAM"; pool_ids = [cloudflare_load_balancer_pool.us.id] }137region_pools { region = "WEU"; pool_ids = [cloudflare_load_balancer_pool.eu.id] }138}139```140141### Secure Admin with Access142143```hcl144resource "cloudflare_pages_project" "admin" { account_id = var.account_id; name = "admin"; production_branch = "main" }145resource "cloudflare_access_application" "admin" {146account_id = var.account_id; name = "Admin"; domain = "admin.example.com"; type = "self_hosted"; session_duration = "24h"147allowed_idps = [cloudflare_access_identity_provider.google.id]148}149resource "cloudflare_access_policy" "allow" {150account_id = var.account_id; application_id = cloudflare_access_application.admin.id151name = "Allow admins"; decision = "allow"; precedence = 1; include { email = var.admin_emails }152}153```154155### Reusable Module156157```hcl158# modules/cloudflare-zone/main.tf159variable "account_id" { type = string }; variable "domain" { type = string }; variable "ssl_mode" { default = "strict" }160resource "cloudflare_zone" "main" { account = { id = var.account_id }; name = var.domain }161resource "cloudflare_zone_settings_override" "main" {162zone_id = cloudflare_zone.main.id; settings { ssl = var.ssl_mode; always_use_https = "on" }163}164output "zone_id" { value = cloudflare_zone.main.id }165166# Usage: module "prod" { source = "./modules/cloudflare-zone"; account_id = var.account_id; domain = "example.com" }167```168169## See Also170171- [README](./README.md) - Provider setup172- [Configuration Reference](./configuration.md) - All resource types173- [API Reference](./api.md) - Data sources174- [Troubleshooting](./gotchas.md) - Best practices, common issues175