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/gotchas.md
1# Terraform Troubleshooting & Best Practices23Common issues, security considerations, and best practices.45## State Drift Issues67Some resources have known state drift. Add lifecycle blocks to prevent perpetual diffs:89| Resource | Drift Attributes | Workaround |10|----------|------------------|------------|11| `cloudflare_pages_project` | `deployment_configs.*` | `ignore_changes = [deployment_configs]` |12| `cloudflare_workers_script` | secrets returned as REDACTED | `ignore_changes = [secret_text_binding]` |13| `cloudflare_load_balancer` | `adaptive_routing`, `random_steering` | `ignore_changes = [adaptive_routing, random_steering]` |14| `cloudflare_workers_kv` | special chars in keys (< 5.16.0) | Upgrade to 5.16.0+ |1516```hcl17# Example: Ignore secret drift18resource "cloudflare_workers_script" "api" {19account_id = var.account_id20name = "api-worker"21content = file("worker.js")22secret_text_binding { name = "API_KEY"; text = var.api_key }2324lifecycle {25ignore_changes = [secret_text_binding]26}27}28```2930## v5 Breaking Changes3132Provider v5 is current (auto-generated from OpenAPI). v4→v5 has breaking changes:3334**Resource Renames:**3536| v4 Resource | v5 Resource | Notes |37|-------------|-------------|-------|38| `cloudflare_record` | `cloudflare_dns_record` | |39| `cloudflare_worker_script` | `cloudflare_workers_script` | Note: plural |40| `cloudflare_worker_*` | `cloudflare_workers_*` | All worker resources |41| `cloudflare_access_*` | `cloudflare_zero_trust_*` | Access → Zero Trust |4243**Attribute Changes:**4445| v4 Attribute | v5 Attribute | Resources |46|--------------|--------------|-----------|47| `zone` | `name` | zone |48| `account_id` | `account.id` | zone (object syntax) |49| `key` | `key_name` | KV |50| `location_hint` | `location` | R2 |5152**State Migration:**5354```bash55# Rename resources in state after v5 upgrade56terraform state mv cloudflare_record.example cloudflare_dns_record.example57terraform state mv cloudflare_worker_script.api cloudflare_workers_script.api58```5960## Resource-Specific Gotchas6162### R2 Location Case Sensitivity6364**Problem:** Terraform creates R2 bucket but fails on subsequent applies65**Cause:** Location must be UPPERCASE66**Solution:** Use `WNAM`, `ENAM`, `WEUR`, `EEUR`, `APAC` (not `wnam`, `enam`, etc.)6768```hcl69resource "cloudflare_r2_bucket" "assets" {70account_id = var.account_id71name = "assets"72location = "WNAM" # UPPERCASE required73}74```7576### KV Special Characters (< 5.16.0)7778**Problem:** Keys with `+`, `#`, `%` cause encoding issues79**Cause:** URL encoding bug in provider < 5.16.080**Solution:** Upgrade to 5.16.0+ or avoid special chars in keys8182### D1 Migrations8384**Problem:** Terraform creates database but schema is empty85**Cause:** Terraform only creates D1 resource, not schema86**Solution:** Run migrations via wrangler after Terraform apply8788```bash89# After terraform apply90wrangler d1 migrations apply <db-name>91```9293### Worker Script Size Limit9495**Problem:** Worker deployment fails with "script too large"96**Cause:** Worker script + dependencies exceed 10 MB limit97**Solution:** Use code splitting, external dependencies, or minification9899### Pages Project Drift100101**Problem:** Pages project shows perpetual diff on `deployment_configs`102**Cause:** Cloudflare API adds default values not in Terraform state103**Solution:** Add lifecycle ignore block (see State Drift table above)104105## Common Errors106107### "Error: couldn't find resource"108109**Cause:** Resource was deleted outside Terraform110**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`111112### "409 Conflict on worker deployment"113114**Cause:** Worker being deployed by both Terraform and wrangler simultaneously115**Solution:** Choose one deployment method; if using Terraform, remove wrangler deployments116117### "DNS record already exists"118119**Cause:** Existing DNS record not imported into Terraform state120**Solution:** Find record ID in Cloudflare dashboard and import with `terraform import cloudflare_dns_record.example <zone-id>/<record-id>`121122### "Invalid provider configuration"123124**Cause:** API token missing, invalid, or lacking required permissions125**Solution:** Set `CLOUDFLARE_API_TOKEN` environment variable or check token permissions in dashboard126127### "State locking errors"128129**Cause:** Multiple concurrent Terraform runs or stale lock from crashed process130**Solution:** Remove stale lock with `terraform force-unlock <lock-id>` (use with caution)131132## Limits133134| Resource | Limit | Notes |135|----------|-------|-------|136| API token rate limit | Varies by plan | Use `api_client_logging = true` to debug137| Worker script size | 10 MB | Includes all dependencies138| KV keys per namespace | Unlimited | Pay per operation139| R2 storage | Unlimited | Pay per GB140| D1 databases | 50,000 per account | Free tier: 10141| Pages projects | 500 per account | 100 for free accounts142| DNS records | 3,500 per zone | Free plan143144## See Also145146- [README](./README.md) - Provider setup147- [Configuration](./configuration.md) - Resources148- [API](./api.md) - Data sources149- [Patterns](./patterns.md) - Use cases150- Provider docs: https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs151