Why I use OpenTofu to manage Cloudflare (even though I am the only developer)

· Tech

I manage a few personal domains on Cloudflare. DNS records, page rules, SSL settings, a tunnel, a Pages project. Nothing crazy. I am the only person who touches this infrastructure, so using an infrastructure-as-code tool like OpenTofu might seem like overkill.

And honestly, it probably is. But I like it. Let me explain why.

Everything lives in one place

I have several domains on Cloudflare. Before OpenTofu, I had to remember which domain has what DNS records, what page rules are active, which email routing is configured where. Now I open my repository and I see it all. One directory, a few files, and the full picture of my infrastructure.

My zone map looks like this:

locals {
zones = {
"example_com" = { name = "example.com" }
"myproject_app" = { name = "myproject.app" }
"myproject_dev" = { name = "myproject.dev" }
"sideproject_dev" = { name = "sideproject.dev" }
}
zone_ids = {
for slug, _ in local.zones :
slug => cloudflare_zone.zones[slug].id
}
}

When I want to add a new domain, I add one line here, create a DNS file for it, and run apply. All zone-wide settings like SSL, DNSSEC, and email routing apply automatically through for_each. I do not need to remember to enable anything manually.

Everything is code

This is the obvious one, but it matters more than I expected. When everything is code, I can read my infrastructure like I read my application code. I do not need to click through Cloudflare dashboard tabs to check if DNSSEC is active on all my zones. I just look at the file:

resource "cloudflare_zone_dnssec" "zones" {
for_each = local.zone_ids
zone_id = each.value
status = "active"
}

One file, one resource, all zones covered. Same for SSL (strict mode on all zones), tiered cache, total TLS, email routing. Each setting gets its own file and applies to every zone automatically.

I can see what changes before I apply them

This is my favorite part. Before anything touches Cloudflare, I run make cf-plan and see exactly what will change. Will it add a record? Modify one? Delete something I did not expect? The plan tells me.

The Cloudflare web interface also validates your input and catches basic mistakes, but the plan gives you something the dashboard cannot: a full overview of all changes at once. When I modify something that affects multiple zones, like a setting that applies to all domains through for_each, the plan shows me every single change across every zone before anything happens. In the web interface, you only see one domain at a time.

I can revert changes

Someone might ask: “Can you rollback with Terraform or OpenTofu?” The honest answer is there is no rollback command. Neither Terraform nor OpenTofu has one.

But it does not matter. My code is in git. My encrypted state is in git. If I mess something up, I revert the commit, run make cf-plan to verify what will change, and run make cf-apply. Done. The infrastructure goes back to the previous state.

This works because the tool does not care about history. It only cares about the current code and the current state. You tell it what should exist, and it makes it happen. So reverting the code and applying is effectively a rollback.

Applying changes is easier than the web interface

My whole workflow is:

  1. Edit a .tf file
  2. make cf-fmt to format the code
  3. make cf-validate to check syntax
  4. make cf-plan to preview changes
  5. make cf-apply to deploy
  6. Commit the updated state to git

Compare that to logging into Cloudflare, finding the right domain, clicking through the right section, editing a record, and repeating that for every domain you need to change.

The Makefile is simple. Every command wraps op run which injects credentials from 1Password at runtime:

cf-plan:
op run --env-file=.op.env -- tofu -chdir=cloudflare plan
cf-apply:
op run --env-file=.op.env -- tofu -chdir=cloudflare apply

No credentials in environment variables. No secrets on disk. Just make cf-plan and everything works.

Everything is on GitHub

The whole repository is on GitHub. Every change I make is a commit with a message. I can go back months and see why I added a specific DNS record or changed a page rule. Try doing that with the Cloudflare dashboard.

The code also works as documentation. If I forget how something is configured, I just read the files instead of clicking around.

AI makes it even easier

Since everything is plain text files, AI tools work really well with this setup. I use AI coding assistants to help me write and edit my configuration. Need to add a new DNS record? I describe what I want, and the AI generates the correct resource block following the naming conventions already in the codebase. Need to add a new zone with a bunch of records? Same thing.

The AI can read the existing files, understand the patterns I use, and produce consistent code. It knows that DNS records should follow the <zone_slug>_<type>_<name> naming convention because it can see the other files. It knows to use local.zone_ids["..."] instead of hardcoding zone IDs.

This would not work as well with a web interface. You cannot ask an AI to click through the Cloudflare dashboard for you. But you can ask it to write or modify a .tf file, review the plan output, and help you understand what will change. Text in, text out. Simple.

Credentials are safe

I use 1Password CLI (op run) to inject secrets at runtime. My .op.env file looks like this:

TF_VAR_cloudflare_api_token="op://Vault/Cloudflare/API Token"
TF_VAR_cloudflare_account_id="op://Vault/Cloudflare/Account ID"
TF_VAR_state_passphrase="op://Vault/State Encryption/credential"

These are just references. The file is safe to commit to git. The actual values live in 1Password and are resolved only when I run a command. They never end up in a file on disk, in shell history, or in logs.

State encryption

OpenTofu has something Terraform does not: built-in state encryption. My state file is encrypted with PBKDF2 and AES-GCM:

terraform {
encryption {
key_provider "pbkdf2" "main" {
passphrase = var.state_passphrase
}
method "aes_gcm" "main" {
keys = key_provider.pbkdf2.main
}
state {
method = method.aes_gcm.main
}
plan {
method = method.aes_gcm.main
}
}
}

Both state and plan files are encrypted. The passphrase comes from 1Password through op run. This means I can commit the state file to git without worrying about leaking sensitive data like API tokens or account IDs. No need for a remote backend, no S3 bucket, no Terraform Cloud. Just an encrypted file in git.

I can recreate everything

If something goes wrong, if I lose access to my Cloudflare account, if I accidentally delete something through the dashboard, I have the full definition of my infrastructure in code. Every DNS record, every page rule, every setting. I can look at it and recreate it manually if needed, or point it at a new account and apply.

That peace of mind is worth the initial setup time.

Is it overkill?

Yes, probably. For a solo developer with a few domains, clicking through the Cloudflare dashboard would be faster for any single change. But over time, the benefits add up. I do not forget configurations. I do not make changes without reviewing them. I have a full history. I have encryption. I have a workflow that takes seconds.

Would I recommend it to every solo developer? Not necessarily. But if you enjoy infrastructure as code and want to learn OpenTofu, managing your Cloudflare setup is a great way to start. The Cloudflare provider is mature, the resources are well-documented, and you get to practice with real infrastructure without the complexity of cloud providers like AWS or GCP.

For me, it just makes my life easier. And that is reason enough.