Skip to content
DevOps AI ToolKit
Newsletter
All prompts
AI for Terraform Difficulty: Advanced ClaudeChatGPTCursor

Terraform Cloudflare DNS Module Design Prompt

Design a Cloudflare provider module that manages a zone and many DNS records via a for_each map while guarding apex and NS records from accidental deletion.

Target user
Platform engineers managing Cloudflare DNS at scale
Difficulty
Advanced
Tools
Claude, ChatGPT, Cursor

The prompt

You are a senior platform engineer designing a reusable Terraform module for Cloudflare DNS using the `cloudflare/cloudflare` provider (>= 4.30). It must manage a zone and a large, changing set of records through a single `records` map with `for_each`, while making apex and delegation records hard to delete by accident.

Work through these steps in order:

1. Configure the provider with an API token (`CLOUDFLARE_API_TOKEN`) scoped to Zone.DNS edit only. Manage the zone with `cloudflare_zone` (or accept a `zone_id` var for pre-existing zones).

2. Model a typed `records` variable as `map(object({ name, type, value/content, ttl, proxied, priority }))`. Iterate with `for_each = var.records` keyed by a stable identifier like `"<name>|<type>"` so adding/removing one record never shifts others.

3. Handle the `proxied` flag correctly: `proxied = true` forces `ttl = 1` (automatic). Validate that `MX`/`TXT`/`NS` records are never proxied, and that CNAME-at-apex uses CNAME flattening.

4. Protect critical records. Apply `lifecycle { prevent_destroy = true }` to the apex `A`/`AAAA`/`CNAME` and any `NS` records, since deleting them causes an immediate outage or NXDOMAIN. Because `prevent_destroy` cannot be conditional per `for_each` instance, split protected records into a separate hardened resource block or module.

5. Warn about DESTRUCTIVE operations: removing a record from the map DELETES it in Cloudflare (traffic loss); changing `type` forces destroy/recreate with a resolution gap; and changing zone name servers or the zone itself can de-delegate the domain entirely.

6. Recommend `terraform plan` gated in CI with a required review on any line containing `will be destroyed`, and importing existing records rather than recreating.

Input template:
```
Zone: <example.com>  (existing zone_id or create new?)
Record count / churn: <e.g. ~200 records, weekly changes>
Records needing proxy (orange cloud): <www, api, ...>
Protected records: <apex A, NS, MX>
Token scope confirmed: <yes/no>
```

Run this prompt with AI

Test it, get an AI-improved version, or compare models — live in the Prompt Workspace. No copy-paste.

Why this prompt works

DNS is a single point of failure: one deleted apex record is an instant outage, and for_each over a map makes it trivially easy to drop a record by editing a variable. This prompt separates the bulk, low-risk records (managed fluidly through a map) from the small set of load-bearing apex and delegation records (hardened with prevent_destroy). It also nails the Cloudflare-specific gotchas around the proxied flag and TTL that break naive configs.

How to use it

Give the model your zone, approximate record count and churn, which hostnames need the orange-cloud proxy, and which records are load-bearing. Ask for two resource blocks: a hardened cloudflare_record set for apex/NS/MX with prevent_destroy, and a bulk for_each block for everything else. Wire a CI check that fails on unreviewed destroys.

Useful commands

# Scoped token (Zone.DNS:Edit) - never a global API key
export CLOUDFLARE_API_TOKEN=...

# Look up the zone id
curl -s -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
  "https://api.cloudflare.com/client/v4/zones?name=example.com" | jq '.result[].id'

# Plan and fail CI on any destructive DNS change
terraform plan -no-color -out=tfplan
terraform show -json tfplan \
  | jq -e '[.resource_changes[] | select(.change.actions | index("delete"))] | length == 0'

# Import an existing record instead of recreating it (v4: <zone_id>/<record_id>)
terraform import 'cloudflare_record.bulk["www|CNAME"]' <zone_id>/<record_id>

# List records to reconcile drift
curl -s -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
  "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records?per_page=100" \
  | jq '.result[] | {name, type, content, proxied}'

Patterns

Bulk records via a stably keyed for_each map:

variable "records" {
  type = map(object({
    name    = string
    type    = string
    content = string
    ttl     = optional(number, 1)
    proxied = optional(bool, false)
    priority = optional(number)
  }))
}

resource "cloudflare_record" "bulk" {
  for_each = var.records

  zone_id  = var.zone_id
  name     = each.value.name
  type     = each.value.type
  content  = each.value.content
  proxied  = each.value.proxied
  # proxied records must use automatic TTL (1)
  ttl      = each.value.proxied ? 1 : each.value.ttl
  priority = each.value.type == "MX" ? each.value.priority : null
}

Hardened apex and delegation records that cannot be destroyed by a map edit:

resource "cloudflare_record" "apex" {
  zone_id = var.zone_id
  name    = "@"
  type    = "A"
  content = var.apex_ip
  proxied = true
  ttl     = 1

  lifecycle {
    prevent_destroy = true
  }
}

resource "cloudflare_record" "mx" {
  for_each = toset(["mx1.example-mail.com", "mx2.example-mail.com"])

  zone_id  = var.zone_id
  name     = "@"
  type     = "MX"
  content  = each.value
  priority = 10
  proxied  = false   # MX must never be proxied
  ttl      = 3600
}

Related prompts

More Terraform prompts & error guides

Browse every Terraform prompt and troubleshooting guide in one place.

Free download · 368-page PDF

Reading prompts? Get all 500 in one free PDF

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.