update skills

This commit is contained in:
2026-03-17 16:53:22 -07:00
parent 0b0783ef8e
commit f9a530667e
389 changed files with 54512 additions and 1 deletions

View File

@@ -0,0 +1,102 @@
# Cloudflare Terraform Provider
**Expert guidance for Cloudflare Terraform Provider - infrastructure as code for Cloudflare resources.**
## Core Principles
- **Provider-first**: Use Terraform provider for ALL infrastructure - never mix with wrangler.jsonc for the same resources
- **State management**: Always use remote state (S3, Terraform Cloud, etc.) for team environments
- **Modular architecture**: Create reusable modules for common patterns (zones, workers, pages)
- **Version pinning**: Always pin provider version with `~>` for predictable upgrades
- **Secret management**: Use variables + environment vars for sensitive data - never hardcode API tokens
## Provider Version
| Version | Status | Notes |
|---------|--------|-------|
| 5.x | Current | Auto-generated from OpenAPI, breaking changes from v4 |
| 4.x | Legacy | Manual maintenance, deprecated |
**Critical:** v5 renamed many resources (`cloudflare_record``cloudflare_dns_record`, `cloudflare_worker_*``cloudflare_workers_*`). See [gotchas.md](./gotchas.md#v5-breaking-changes) for migration details.
## Provider Setup
### Basic Configuration
```hcl
terraform {
required_version = ">= 1.0"
required_providers {
cloudflare = {
source = "cloudflare/cloudflare"
version = "~> 5.15.0"
}
}
}
provider "cloudflare" {
api_token = var.cloudflare_api_token # or CLOUDFLARE_API_TOKEN env var
}
```
### Authentication Methods (priority order)
1. **API Token** (RECOMMENDED): `api_token` or `CLOUDFLARE_API_TOKEN`
- Create: Dashboard → My Profile → API Tokens
- Scope to specific accounts/zones for security
2. **Global API Key** (LEGACY): `api_key` + `api_email` or `CLOUDFLARE_API_KEY` + `CLOUDFLARE_EMAIL`
- Less secure, use tokens instead
3. **User Service Key**: `user_service_key` for Origin CA certificates
## Quick Reference: Common Commands
```bash
terraform init # Initialize provider
terraform plan # Plan changes
terraform apply # Apply changes
terraform destroy # Destroy resources
terraform import cloudflare_zone.example <zone-id> # Import existing
terraform state list # List resources in state
terraform output # Show outputs
terraform fmt -recursive # Format code
terraform validate # Validate configuration
```
## Import Existing Resources
Use cf-terraforming to generate configs from existing Cloudflare resources:
```bash
# Install
brew install cloudflare/cloudflare/cf-terraforming
# Generate HCL from existing resources
cf-terraforming generate --resource-type cloudflare_dns_record --zone <zone-id>
# Import into Terraform state
cf-terraforming import --resource-type cloudflare_dns_record --zone <zone-id>
```
## Reading Order
1. Start with [README.md](./README.md) for provider setup and authentication
2. Review [configuration.md](./configuration.md) for resource configurations
3. Check [api.md](./api.md) for data sources and existing resource queries
4. See [patterns.md](./patterns.md) for multi-environment and CI/CD patterns
5. Read [gotchas.md](./gotchas.md) for state drift, v5 breaking changes, and troubleshooting
## In This Reference
- [configuration.md](./configuration.md) - Resources for zones, DNS, workers, KV, R2, D1, Pages, rulesets
- [api.md](./api.md) - Data sources for existing resources
- [patterns.md](./patterns.md) - Architecture patterns, multi-env setup, CI/CD integration
- [gotchas.md](./gotchas.md) - Common issues, security, best practices
## See Also
- [pulumi](../pulumi/) - Alternative IaC tool for Cloudflare
- [wrangler](../wrangler/) - CLI deployment alternative
- [workers](../workers/) - Worker runtime documentation

View File

@@ -0,0 +1,178 @@
# Terraform Data Sources Reference
Query existing Cloudflare resources to reference in your configurations.
## v5 Data Source Names
| v4 Name | v5 Name | Notes |
|---------|---------|-------|
| `cloudflare_record` | `cloudflare_dns_record` | |
| `cloudflare_worker_script` | `cloudflare_workers_script` | Note: plural |
| `cloudflare_access_*` | `cloudflare_zero_trust_*` | Access → Zero Trust |
## Zone Data Sources
```hcl
# Get zone by name
data "cloudflare_zone" "example" {
name = "example.com"
}
# Use in resources
resource "cloudflare_dns_record" "www" {
zone_id = data.cloudflare_zone.example.id
name = "www"
# ...
}
```
## Account Data Sources
```hcl
# List all accounts
data "cloudflare_accounts" "main" {
name = "My Account"
}
# Use account ID
resource "cloudflare_worker_script" "api" {
account_id = data.cloudflare_accounts.main.accounts[0].id
# ...
}
```
## Worker Data Sources
```hcl
# Get existing worker script (v5: cloudflare_workers_script)
data "cloudflare_workers_script" "existing" {
account_id = var.account_id
name = "existing-worker"
}
# Reference in service bindings
resource "cloudflare_workers_script" "consumer" {
service_binding {
name = "UPSTREAM"
service = data.cloudflare_workers_script.existing.name
}
}
```
## KV Data Sources
```hcl
# Get KV namespace
data "cloudflare_workers_kv_namespace" "existing" {
account_id = var.account_id
namespace_id = "abc123"
}
# Use in worker binding
resource "cloudflare_workers_script" "api" {
kv_namespace_binding {
name = "KV"
namespace_id = data.cloudflare_workers_kv_namespace.existing.id
}
}
```
## Lists Data Source
```hcl
# Get IP lists for WAF rules
data "cloudflare_list" "blocked_ips" {
account_id = var.account_id
name = "blocked_ips"
}
```
## IP Ranges Data Source
```hcl
# Get Cloudflare IP ranges (for firewall rules)
data "cloudflare_ip_ranges" "cloudflare" {}
output "ipv4_cidrs" {
value = data.cloudflare_ip_ranges.cloudflare.ipv4_cidr_blocks
}
output "ipv6_cidrs" {
value = data.cloudflare_ip_ranges.cloudflare.ipv6_cidr_blocks
}
# Use in security group rules (AWS example)
resource "aws_security_group_rule" "allow_cloudflare" {
type = "ingress"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = data.cloudflare_ip_ranges.cloudflare.ipv4_cidr_blocks
security_group_id = aws_security_group.web.id
}
```
## Common Patterns
### Import ID Formats
| Resource | Import ID Format |
|----------|------------------|
| `cloudflare_zone` | `<zone-id>` |
| `cloudflare_dns_record` | `<zone-id>/<record-id>` |
| `cloudflare_workers_script` | `<account-id>/<script-name>` |
| `cloudflare_workers_kv_namespace` | `<account-id>/<namespace-id>` |
| `cloudflare_r2_bucket` | `<account-id>/<bucket-name>` |
| `cloudflare_d1_database` | `<account-id>/<database-id>` |
| `cloudflare_pages_project` | `<account-id>/<project-name>` |
```bash
# Example: Import DNS record
terraform import cloudflare_dns_record.example <zone-id>/<record-id>
```
### Reference Across Modules
```hcl
# modules/worker/main.tf
data "cloudflare_zone" "main" {
name = var.domain
}
resource "cloudflare_worker_route" "api" {
zone_id = data.cloudflare_zone.main.id
pattern = "api.${var.domain}/*"
script_name = cloudflare_worker_script.api.name
}
```
### Output Important Values
```hcl
output "zone_id" {
value = cloudflare_zone.main.id
description = "Zone ID for DNS management"
}
output "worker_url" {
value = "https://${cloudflare_worker_domain.api.hostname}"
description = "Worker API endpoint"
}
output "kv_namespace_id" {
value = cloudflare_workers_kv_namespace.app.id
sensitive = false
}
output "name_servers" {
value = cloudflare_zone.main.name_servers
description = "Name servers for domain registration"
}
```
## See Also
- [README](./README.md) - Provider setup
- [Configuration Reference](./configuration.md) - All resource types
- [Patterns](./patterns.md) - Architecture patterns
- [Troubleshooting](./gotchas.md) - Common issues

View File

@@ -0,0 +1,197 @@
# Terraform Configuration Reference
Complete resource configurations for Cloudflare infrastructure.
## Zone & DNS
```hcl
# Zone + settings
resource "cloudflare_zone" "example" { account = { id = var.account_id }; name = "example.com"; type = "full" }
resource "cloudflare_zone_settings_override" "example" {
zone_id = cloudflare_zone.example.id
settings { ssl = "strict"; always_use_https = "on"; min_tls_version = "1.2"; tls_1_3 = "on"; http3 = "on" }
}
# DNS records (A, CNAME, MX, TXT)
resource "cloudflare_dns_record" "www" {
zone_id = cloudflare_zone.example.id; name = "www"; content = "192.0.2.1"; type = "A"; proxied = true
}
resource "cloudflare_dns_record" "mx" {
for_each = { "10" = "mail1.example.com", "20" = "mail2.example.com" }
zone_id = cloudflare_zone.example.id; name = "@"; content = each.value; type = "MX"; priority = each.key
}
```
## Workers
### Simple Pattern (Legacy - Still Works)
```hcl
resource "cloudflare_workers_script" "api" {
account_id = var.account_id; name = "api-worker"; content = file("worker.js")
module = true; compatibility_date = "2025-01-01"
kv_namespace_binding { name = "KV"; namespace_id = cloudflare_workers_kv_namespace.cache.id }
r2_bucket_binding { name = "BUCKET"; bucket_name = cloudflare_r2_bucket.assets.name }
d1_database_binding { name = "DB"; database_id = cloudflare_d1_database.app.id }
secret_text_binding { name = "SECRET"; text = var.secret }
}
```
### Gradual Rollouts (Recommended for Production)
```hcl
resource "cloudflare_worker" "api" { account_id = var.account_id; name = "api-worker" }
resource "cloudflare_worker_version" "api_v1" {
account_id = var.account_id; worker_name = cloudflare_worker.api.name
content = file("worker.js"); content_sha256 = filesha256("worker.js")
compatibility_date = "2025-01-01"
bindings {
kv_namespace { name = "KV"; namespace_id = cloudflare_workers_kv_namespace.cache.id }
r2_bucket { name = "BUCKET"; bucket_name = cloudflare_r2_bucket.assets.name }
}
}
resource "cloudflare_workers_deployment" "api" {
account_id = var.account_id; worker_name = cloudflare_worker.api.name
versions { version_id = cloudflare_worker_version.api_v1.id; percentage = 100 }
}
```
### Worker Binding Types (v5)
| Binding | Attribute | Example |
|---------|-----------|---------|
| KV | `kv_namespace_binding` | `{ name = "KV", namespace_id = "..." }` |
| R2 | `r2_bucket_binding` | `{ name = "BUCKET", bucket_name = "..." }` |
| D1 | `d1_database_binding` | `{ name = "DB", database_id = "..." }` |
| Service | `service_binding` | `{ name = "AUTH", service = "auth-worker" }` |
| Secret | `secret_text_binding` | `{ name = "API_KEY", text = "..." }` |
| Queue | `queue_binding` | `{ name = "QUEUE", queue_name = "..." }` |
| Vectorize | `vectorize_binding` | `{ name = "INDEX", index_name = "..." }` |
| Hyperdrive | `hyperdrive_binding` | `{ name = "DB", id = "..." }` |
| AI | `ai_binding` | `{ name = "AI" }` |
| Browser | `browser_binding` | `{ name = "BROWSER" }` |
| Analytics | `analytics_engine_binding` | `{ name = "ANALYTICS", dataset = "..." }` |
| mTLS | `mtls_certificate_binding` | `{ name = "CERT", certificate_id = "..." }` |
### Routes & Triggers
```hcl
resource "cloudflare_worker_route" "api" {
zone_id = cloudflare_zone.example.id; pattern = "api.example.com/*"
script_name = cloudflare_workers_script.api.name
}
resource "cloudflare_worker_cron_trigger" "task" {
account_id = var.account_id; script_name = cloudflare_workers_script.api.name
schedules = ["*/5 * * * *"]
}
```
## Storage (KV, R2, D1)
```hcl
# KV
resource "cloudflare_workers_kv_namespace" "cache" { account_id = var.account_id; title = "cache" }
resource "cloudflare_workers_kv" "config" {
account_id = var.account_id; namespace_id = cloudflare_workers_kv_namespace.cache.id
key_name = "config"; value = jsonencode({ version = "1.0" })
}
# R2
resource "cloudflare_r2_bucket" "assets" { account_id = var.account_id; name = "assets"; location = "WNAM" }
# D1 (migrations via wrangler) & Queues
resource "cloudflare_d1_database" "app" { account_id = var.account_id; name = "app-db" }
resource "cloudflare_queue" "events" { account_id = var.account_id; name = "events-queue" }
```
## Pages
```hcl
resource "cloudflare_pages_project" "site" {
account_id = var.account_id; name = "site"; production_branch = "main"
deployment_configs {
production {
compatibility_date = "2025-01-01"
environment_variables = { NODE_ENV = "production" }
kv_namespaces = { KV = cloudflare_workers_kv_namespace.cache.id }
d1_databases = { DB = cloudflare_d1_database.app.id }
}
}
build_config { build_command = "npm run build"; destination_dir = "dist" }
source { type = "github"; config { owner = "org"; repo_name = "site"; production_branch = "main" }}
}
resource "cloudflare_pages_domain" "custom" {
account_id = var.account_id; project_name = cloudflare_pages_project.site.name; domain = "site.example.com"
}
```
## Rulesets (WAF, Redirects, Cache)
```hcl
# WAF
resource "cloudflare_ruleset" "waf" {
zone_id = cloudflare_zone.example.id; name = "WAF"; kind = "zone"; phase = "http_request_firewall_custom"
rules { action = "block"; enabled = true; expression = "(cf.client.bot) and not (cf.verified_bot)" }
}
# Redirects
resource "cloudflare_ruleset" "redirects" {
zone_id = cloudflare_zone.example.id; name = "Redirects"; kind = "zone"; phase = "http_request_dynamic_redirect"
rules {
action = "redirect"; enabled = true; expression = "(http.request.uri.path eq \"/old\")"
action_parameters { from_value { status_code = 301; target_url { value = "https://example.com/new" }}}
}
}
# Cache rules
resource "cloudflare_ruleset" "cache" {
zone_id = cloudflare_zone.example.id; name = "Cache"; kind = "zone"; phase = "http_request_cache_settings"
rules {
action = "set_cache_settings"; enabled = true; expression = "(http.request.uri.path matches \"\\.(jpg|png|css|js)$\")"
action_parameters { cache = true; edge_ttl { mode = "override_origin"; default = 86400 }}
}
}
```
## Load Balancers
```hcl
resource "cloudflare_load_balancer_monitor" "http" {
account_id = var.account_id; type = "http"; path = "/health"; interval = 60; timeout = 5
}
resource "cloudflare_load_balancer_pool" "api" {
account_id = var.account_id; name = "api-pool"; monitor = cloudflare_load_balancer_monitor.http.id
origins { name = "api-1"; address = "192.0.2.1" }
origins { name = "api-2"; address = "192.0.2.2" }
}
resource "cloudflare_load_balancer" "api" {
zone_id = cloudflare_zone.example.id; name = "api.example.com"
default_pool_ids = [cloudflare_load_balancer_pool.api.id]; steering_policy = "geo"
}
```
## Access (Zero Trust)
```hcl
resource "cloudflare_access_application" "admin" {
account_id = var.account_id; name = "Admin"; domain = "admin.example.com"; type = "self_hosted"
session_duration = "24h"; allowed_idps = [cloudflare_access_identity_provider.github.id]
}
resource "cloudflare_access_policy" "allow" {
account_id = var.account_id; application_id = cloudflare_access_application.admin.id
name = "Allow"; decision = "allow"; precedence = 1
include { email = ["admin@example.com"] }
}
resource "cloudflare_access_identity_provider" "github" {
account_id = var.account_id; name = "GitHub"; type = "github"
config { client_id = var.github_id; client_secret = var.github_secret }
}
```
## See Also
- [README](./README.md) - Provider setup
- [API](./api.md) - Data sources
- [Patterns](./patterns.md) - Use cases
- [Troubleshooting](./gotchas.md) - Issues

View File

@@ -0,0 +1,150 @@
# Terraform Troubleshooting & Best Practices
Common issues, security considerations, and best practices.
## State Drift Issues
Some resources have known state drift. Add lifecycle blocks to prevent perpetual diffs:
| Resource | Drift Attributes | Workaround |
|----------|------------------|------------|
| `cloudflare_pages_project` | `deployment_configs.*` | `ignore_changes = [deployment_configs]` |
| `cloudflare_workers_script` | secrets returned as REDACTED | `ignore_changes = [secret_text_binding]` |
| `cloudflare_load_balancer` | `adaptive_routing`, `random_steering` | `ignore_changes = [adaptive_routing, random_steering]` |
| `cloudflare_workers_kv` | special chars in keys (< 5.16.0) | Upgrade to 5.16.0+ |
```hcl
# Example: Ignore secret drift
resource "cloudflare_workers_script" "api" {
account_id = var.account_id
name = "api-worker"
content = file("worker.js")
secret_text_binding { name = "API_KEY"; text = var.api_key }
lifecycle {
ignore_changes = [secret_text_binding]
}
}
```
## v5 Breaking Changes
Provider v5 is current (auto-generated from OpenAPI). v4→v5 has breaking changes:
**Resource Renames:**
| v4 Resource | v5 Resource | Notes |
|-------------|-------------|-------|
| `cloudflare_record` | `cloudflare_dns_record` | |
| `cloudflare_worker_script` | `cloudflare_workers_script` | Note: plural |
| `cloudflare_worker_*` | `cloudflare_workers_*` | All worker resources |
| `cloudflare_access_*` | `cloudflare_zero_trust_*` | Access → Zero Trust |
**Attribute Changes:**
| v4 Attribute | v5 Attribute | Resources |
|--------------|--------------|-----------|
| `zone` | `name` | zone |
| `account_id` | `account.id` | zone (object syntax) |
| `key` | `key_name` | KV |
| `location_hint` | `location` | R2 |
**State Migration:**
```bash
# Rename resources in state after v5 upgrade
terraform state mv cloudflare_record.example cloudflare_dns_record.example
terraform state mv cloudflare_worker_script.api cloudflare_workers_script.api
```
## Resource-Specific Gotchas
### R2 Location Case Sensitivity
**Problem:** Terraform creates R2 bucket but fails on subsequent applies
**Cause:** Location must be UPPERCASE
**Solution:** Use `WNAM`, `ENAM`, `WEUR`, `EEUR`, `APAC` (not `wnam`, `enam`, etc.)
```hcl
resource "cloudflare_r2_bucket" "assets" {
account_id = var.account_id
name = "assets"
location = "WNAM" # UPPERCASE required
}
```
### KV Special Characters (< 5.16.0)
**Problem:** Keys with `+`, `#`, `%` cause encoding issues
**Cause:** URL encoding bug in provider < 5.16.0
**Solution:** Upgrade to 5.16.0+ or avoid special chars in keys
### D1 Migrations
**Problem:** Terraform creates database but schema is empty
**Cause:** Terraform only creates D1 resource, not schema
**Solution:** Run migrations via wrangler after Terraform apply
```bash
# After terraform apply
wrangler d1 migrations apply <db-name>
```
### Worker Script Size Limit
**Problem:** Worker deployment fails with "script too large"
**Cause:** Worker script + dependencies exceed 10 MB limit
**Solution:** Use code splitting, external dependencies, or minification
### Pages Project Drift
**Problem:** Pages project shows perpetual diff on `deployment_configs`
**Cause:** Cloudflare API adds default values not in Terraform state
**Solution:** Add lifecycle ignore block (see State Drift table above)
## Common Errors
### "Error: couldn't find resource"
**Cause:** Resource was deleted outside Terraform
**Solution:** Import resource back into state with `terraform import cloudflare_zone.example <zone-id>` or remove from state with `terraform state rm cloudflare_zone.example`
### "409 Conflict on worker deployment"
**Cause:** Worker being deployed by both Terraform and wrangler simultaneously
**Solution:** Choose one deployment method; if using Terraform, remove wrangler deployments
### "DNS record already exists"
**Cause:** Existing DNS record not imported into Terraform state
**Solution:** Find record ID in Cloudflare dashboard and import with `terraform import cloudflare_dns_record.example <zone-id>/<record-id>`
### "Invalid provider configuration"
**Cause:** API token missing, invalid, or lacking required permissions
**Solution:** Set `CLOUDFLARE_API_TOKEN` environment variable or check token permissions in dashboard
### "State locking errors"
**Cause:** Multiple concurrent Terraform runs or stale lock from crashed process
**Solution:** Remove stale lock with `terraform force-unlock <lock-id>` (use with caution)
## Limits
| Resource | Limit | Notes |
|----------|-------|-------|
| API token rate limit | Varies by plan | Use `api_client_logging = true` to debug
| Worker script size | 10 MB | Includes all dependencies
| KV keys per namespace | Unlimited | Pay per operation
| R2 storage | Unlimited | Pay per GB
| D1 databases | 50,000 per account | Free tier: 10
| Pages projects | 500 per account | 100 for free accounts
| DNS records | 3,500 per zone | Free plan
## See Also
- [README](./README.md) - Provider setup
- [Configuration](./configuration.md) - Resources
- [API](./api.md) - Data sources
- [Patterns](./patterns.md) - Use cases
- Provider docs: https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs

View File

@@ -0,0 +1,174 @@
# Terraform Patterns & Use Cases
Architecture patterns, multi-environment setups, and real-world use cases.
## Recommended Directory Structure
```
terraform/
├── environments/
│ ├── production/
│ │ ├── main.tf
│ │ └── terraform.tfvars
│ └── staging/
│ ├── main.tf
│ └── terraform.tfvars
├── modules/
│ ├── zone/
│ ├── worker/
│ └── dns/
└── shared/ # Shared resources across envs
└── main.tf
```
**Note:** Cloudflare recommends avoiding modules for provider resources due to v5 auto-generation complexity. Prefer environment directories + shared state instead.
## Multi-Environment Setup
```hcl
# Directory: environments/{production,staging}/main.tf + modules/{zone,worker,pages}
module "zone" {
source = "../../modules/zone"; account_id = var.account_id; zone_name = "example.com"; environment = "production"
}
module "api_worker" {
source = "../../modules/worker"; account_id = var.account_id; zone_id = module.zone.zone_id
name = "api-worker-prod"; script = file("../../workers/api.js"); environment = "production"
}
```
## R2 State Backend
```hcl
terraform {
backend "s3" {
bucket = "terraform-state"
key = "cloudflare.tfstate"
region = "auto"
endpoints = { s3 = "https://<account_id>.r2.cloudflarestorage.com" }
skip_credentials_validation = true
skip_region_validation = true
skip_requesting_account_id = true
skip_metadata_api_check = true
skip_s3_checksum = true
}
}
```
## Worker with All Bindings
```hcl
locals { worker_name = "full-stack-worker" }
resource "cloudflare_workers_kv_namespace" "app" { account_id = var.account_id; title = "${local.worker_name}-kv" }
resource "cloudflare_r2_bucket" "app" { account_id = var.account_id; name = "${local.worker_name}-bucket" }
resource "cloudflare_d1_database" "app" { account_id = var.account_id; name = "${local.worker_name}-db" }
resource "cloudflare_worker_script" "app" {
account_id = var.account_id; name = local.worker_name; content = file("worker.js"); module = true
compatibility_date = "2025-01-01"
kv_namespace_binding { name = "KV"; namespace_id = cloudflare_workers_kv_namespace.app.id }
r2_bucket_binding { name = "BUCKET"; bucket_name = cloudflare_r2_bucket.app.name }
d1_database_binding { name = "DB"; database_id = cloudflare_d1_database.app.id }
secret_text_binding { name = "API_KEY"; text = var.api_key }
}
```
## Wrangler Integration
**CRITICAL**: Wrangler and Terraform must NOT manage same resources.
**Terraform**: Zones, DNS, security rules, Access, load balancers, worker deployments (CI/CD), KV/R2/D1 resource creation
**Wrangler**: Local dev (`wrangler dev`), manual deploys, D1 migrations, KV bulk ops, log streaming (`wrangler tail`)
### CI/CD Pattern
```hcl
# Terraform creates infrastructure
resource "cloudflare_workers_kv_namespace" "app" { account_id = var.account_id; title = "app-kv" }
resource "cloudflare_d1_database" "app" { account_id = var.account_id; name = "app-db" }
output "kv_namespace_id" { value = cloudflare_workers_kv_namespace.app.id }
output "d1_database_id" { value = cloudflare_d1_database.app.id }
```
```yaml
# GitHub Actions: terraform apply → envsubst wrangler.jsonc.template → wrangler deploy
- run: terraform apply -auto-approve
- run: |
export KV_NAMESPACE_ID=$(terraform output -raw kv_namespace_id)
envsubst < wrangler.jsonc.template > wrangler.jsonc
- run: wrangler deploy
```
## Use Cases
### Static Site + API Worker
```hcl
resource "cloudflare_pages_project" "frontend" {
account_id = var.account_id; name = "frontend"; production_branch = "main"
build_config { build_command = "npm run build"; destination_dir = "dist" }
}
resource "cloudflare_worker_script" "api" {
account_id = var.account_id; name = "api"; content = file("api-worker.js")
d1_database_binding { name = "DB"; database_id = cloudflare_d1_database.api_db.id }
}
resource "cloudflare_dns_record" "frontend" {
zone_id = cloudflare_zone.main.id; name = "app"; content = cloudflare_pages_project.frontend.subdomain; type = "CNAME"; proxied = true
}
resource "cloudflare_worker_route" "api" {
zone_id = cloudflare_zone.main.id; pattern = "api.example.com/*"; script_name = cloudflare_worker_script.api.name
}
```
### Multi-Region Load Balancing
```hcl
resource "cloudflare_load_balancer_pool" "us" {
account_id = var.account_id; name = "us-pool"; monitor = cloudflare_load_balancer_monitor.http.id
origins { name = "us-east"; address = var.us_east_ip }
}
resource "cloudflare_load_balancer_pool" "eu" {
account_id = var.account_id; name = "eu-pool"; monitor = cloudflare_load_balancer_monitor.http.id
origins { name = "eu-west"; address = var.eu_west_ip }
}
resource "cloudflare_load_balancer" "global" {
zone_id = cloudflare_zone.main.id; name = "api.example.com"; steering_policy = "geo"
default_pool_ids = [cloudflare_load_balancer_pool.us.id]
region_pools { region = "WNAM"; pool_ids = [cloudflare_load_balancer_pool.us.id] }
region_pools { region = "WEU"; pool_ids = [cloudflare_load_balancer_pool.eu.id] }
}
```
### Secure Admin with Access
```hcl
resource "cloudflare_pages_project" "admin" { account_id = var.account_id; name = "admin"; production_branch = "main" }
resource "cloudflare_access_application" "admin" {
account_id = var.account_id; name = "Admin"; domain = "admin.example.com"; type = "self_hosted"; session_duration = "24h"
allowed_idps = [cloudflare_access_identity_provider.google.id]
}
resource "cloudflare_access_policy" "allow" {
account_id = var.account_id; application_id = cloudflare_access_application.admin.id
name = "Allow admins"; decision = "allow"; precedence = 1; include { email = var.admin_emails }
}
```
### Reusable Module
```hcl
# modules/cloudflare-zone/main.tf
variable "account_id" { type = string }; variable "domain" { type = string }; variable "ssl_mode" { default = "strict" }
resource "cloudflare_zone" "main" { account = { id = var.account_id }; name = var.domain }
resource "cloudflare_zone_settings_override" "main" {
zone_id = cloudflare_zone.main.id; settings { ssl = var.ssl_mode; always_use_https = "on" }
}
output "zone_id" { value = cloudflare_zone.main.id }
# Usage: module "prod" { source = "./modules/cloudflare-zone"; account_id = var.account_id; domain = "example.com" }
```
## See Also
- [README](./README.md) - Provider setup
- [Configuration Reference](./configuration.md) - All resource types
- [API Reference](./api.md) - Data sources
- [Troubleshooting](./gotchas.md) - Best practices, common issues