Skip to content

AutomationOnboarding stage

Cloudflare as code: import an existing account with cf-terraforming

Generate Terraform configuration and import blocks from a live Cloudflare account with cf-terraforming, then review, pin the provider, and apply a baseline.

On this page12 sections

Short answer

How do you import an existing Cloudflare account into Terraform?

Use cf-terraforming to generate Terraform configuration and import blocks from the live account, review every line, then run plan and apply so Terraform adopts the resources without changing them. Pin the provider version, keep state remote, review a plan before every apply, and treat Git history as the rollback. The tool is for the first import, not for CI.

Who this is for: engineers who already have zones, rulesets, and Access policies built by hand in the dashboard, and want them in version control without recreating them. We keep Cloudflare baselines in Terraform and import existing configuration with cf-terraforming.

Version box: what this article was checked against

Item Version How it was checked Date
cf-terraforming v0.29.0, published 2026-09-02 GitHub releases API 2026-10-04
Provider cloudflare/cloudflare on the Terraform registry 5.26.0, published 2026-09-26 Terraform registry API 2026-10-04
Provider on GitHub v5.27.0, published 2026-10-03, not yet on the registry GitHub releases and changelog 2026-10-04
Terraform CLI 1.16.5, the latest release on HashiCorp’s install page HashiCorp install page 2026-10-04
Commands in this article Checked against the README and the documentation; not executed for this article Documentation review 2026-10-04

Recheck the box when you use the article.

What does cf-terraforming do, and what does it not do?

cf-terraforming is a Cloudflare command-line tool that reads your configuration from the Cloudflare API with your credentials and writes Terraform HCL for the Cloudflare provider. Its README describes the intent as a one-time generation, after which Terraform manages the resources, and warns: “This tool is not intended for use in CI.”

Two consequences. First, run it from an engineer’s machine and commit the reviewed result; CI runs only terraform plan and terraform apply. Second, the README notes that some generated resources may fail terraform validate because of schema inconsistencies. Generated code is a starting point, not a deliverable. HashiCorp says the same about Terraform’s own generated configuration: review and edit it before adding it to version control.

What do you need before you start?

The README lists a Cloudflare account with resources, a credential with permission to read them, and a Terraform directory where terraform init has run and the providers are installed. Pin the provider version you intend to use before that init. If no Terraform binary is on the path, the tool downloads the latest Terraform, so point --terraform-binary-path at the version you pinned.

Use a scoped API token rather than a global key. Cloudflare’s token guide says most permission groups offer Edit or Read, tokens can be limited to specific accounts or zones, and both a time-to-live and client IP filtering are optional settings. The secret is shown once; keep it in a secrets manager, not in a file.

export CLOUDFLARE_API_TOKEN='...'   # read-only, short TTL
export CLOUDFLARE_ZONE_ID='...'

How do you generate configuration and import blocks?

Run generate for the configuration, then import for the matching import statements.

cf-terraforming generate \
  --zone $CLOUDFLARE_ZONE_ID \
  --resource-type "cloudflare_dns_record" > dns.tf

cf-terraforming import \
  --zone $CLOUDFLARE_ZONE_ID \
  --resource-type "cloudflare_dns_record" \
  --modern-import-block > imports.tf

Things to know:

  • --modern-import-block writes HCL import blocks, which need Terraform 1.5 or later. Without it, the tool prints terraform import commands that work with all versions.
  • --resource-type accepts a comma-separated list, so one run can cover several types.
  • Resource names. The v5 tables list cloudflare_dns_record; cloudflare_record belongs to the v4 provider, although some README examples still use it.
  • IDs. Resources that need an ID take --resource-id as type=id1,id2, for example cloudflare_hostname_tls_setting=ciphers,min_tls_version. Account-level resources take --account instead of --zone.
  • Gaps in the import table. In v0.29.0, cloudflare_zone_setting, cloudflare_zone, and cloudflare_zero_trust_access_policy appear under generate but not import. Write their import blocks by hand with the provider’s import IDs, such as <zone_id>/<setting_id> for a zone setting and <account_id>/<policy_id> for an Access policy.

With import blocks, terraform plan lists the resources to import and nothing to add, change, or destroy; terraform apply performs the import; a second plan should then report no changes. Cloudflare’s own import page still shows the older v4 command flow.

How do you review the generated code?

Read the diff the way you would review a pull request from a stranger:

  1. terraform validate passes. Fix schema inconsistencies by hand where the README warns about them.
  2. The plan shows imports only, and the plan after apply is clean. Any change is a bug in the generated code or real drift; decide which before going further. The provider’s v5 migration guide documents some perpetual differences in computed fields.
  3. Generated names such as terraform_managed_resource_<id>_<n> become meaningful names. Rename the resource block and the import block’s to address together, before the first apply; afterward, use moved blocks.
  4. Hard-coded zone IDs, account IDs, and secrets become variables or data sources.
  5. Anything changed in the dashboard since the export is exported again.

How do you organize it? The zone baseline checklist

Cloudflare’s Terraform best practices recommend splitting configuration into directories by account, zone, and product, each with its own state, and using modules sparingly, because a shared module can leave resources out of sync or recreated. The zone baseline checklist follows that guidance. It is our working list, not a Cloudflare standard.

  • One directory per account and zone, split by product where that helps, each with its own state.
  • One reviewed baseline per zone class, copied into each zone’s directory. If you use a module, keep it small and pin it to an exact version or Git reference: the lock file does not pin modules.
  • TLS mode, minimum TLS version, and HSTS set explicitly; in v5 these are cloudflare_zone_setting resources (ssl, min_tls_version, security_header).
  • WAF custom rules and rate limiting rules kept with their zone, so per-zone differences are visible.
  • DNS records per zone.
  • Provider version constrained in required_providers, and .terraform.lock.hcl committed.
  • Remote state, with access limited to the people and pipelines that need it.
  • A README listing what Terraform does not manage.

HashiCorp’s documentation supports two of these directly. The dependency lock file records provider versions, and “you should include this file in your version control repository” so changes go through review; terraform init -upgrade deliberately selects newer allowed versions. Terraform also writes secret values into state and plan files, and local state is plaintext, so HashiCorp recommends remote storage, encryption at rest, access controls, and audit logging.

How do you handle provider upgrades and renamed resources?

Pin first, upgrade deliberately, and use the provider’s own tools. Even a minor release can carry a breaking change: the changelog for 5.27.0 lists one.

For v4 to v5, the provider’s version 5 migration guide is the path: move to v4.52.5 first if you run an older v4 release, run tf-migrate, the official migration tool, on the configuration, change the constraint to ~> 5, run terraform init -upgrade, then plan and apply. tf-migrate handles resource type renames, attribute renames, and block restructuring, such as cloudflare_access_application becoming cloudflare_zero_trust_access_application.

The renamed-resources guide offers three other routes: terraform state rm followed by an import of the new resource; moved blocks on Terraform 1.8 and later, which cover address changes but not attribute renames, and which only some renamed types support; and a two-phase swap. Its preconditions apply to all: freeze other changes, back up configuration and state, and rehearse on a non-production environment or a small subset first.

How do you run it in CI?

Our practice: run terraform plan on every pull request, have a person approve the plan, and apply that saved plan (terraform plan -out) only from the main branch. The README’s warning concerns cf-terraforming, not Terraform: the generator stays on a workstation.

Schedule a plan with -detailed-exitcode to catch drift; exit code 2 means changes are present. A change can come from an edit in the dashboard or API, or from provider behavior after an upgrade. Decide whether to revert it or adopt it into code.

What this article has not tested

  • The commands were not run against the versions in the box for this article.
  • Accounts with very large numbers of zones; no timings or rate-limit figures are given.
  • OpenTofu: the README says a Terraform-compatible binary such as tofu can be supplied with --terraform-binary-path; nothing more is claimed here.
  • Not every resource type generates or imports cleanly; check the README tables for yours.

FAQ

Can cf-terraforming run in a CI pipeline?

Its README says the tool is not intended for use in CI and describes it as a one-time generation of configuration. Run it on a workstation, review the output, and commit the result. Let the pipeline run terraform plan and terraform apply against the committed code, with a person approving each plan before it is applied.

Does cf-terraforming support provider v5?

Yes, with gaps. The README’s v5 tables list the types that can be generated and the types that can be imported, and the import table is shorter. Types missing from it, such as zone settings and Access policies in v0.29.0, need import blocks written by hand. Some generated resources may also fail validation and need edits.

What are modern import blocks?

They are HCL import blocks, produced when you pass --modern-import-block, and they need Terraform 1.5 or later. Without the flag, the tool prints terraform import commands that work with all versions. With import blocks, terraform plan shows what will be imported and terraform apply performs the import, after which a plan should be clean.

How do I move from provider v4 to v5?

Follow the provider’s version 5 migration guide. Move to v4.52.5 first if you run an older v4 release, run the official tf-migrate tool on the configuration, change the version constraint to ~> 5, and run terraform init -upgrade, then plan and apply. Back up state first and rehearse on a small subset or a non-production account.

What API token permissions does the import need?

Read permissions on the resource types you export, scoped to the accounts or zones in scope, with a short time-to-live. Cloudflare’s token settings also allow client IP filtering. Keep the token out of files and out of state, and give the pipeline its own token, scoped for the changes it applies rather than for administration.

Where should Terraform state live?

In a remote backend with encryption and access controls. HashiCorp’s documentation says Terraform writes sensitive values into state and plan files and that local state is stored in plaintext, so it recommends remote storage, encryption at rest, access limits, and audit logging. Which backend you choose is your decision; this article does not recommend a product.

Sources

Checked against vendor documentation on How the articles are checked

  1. cf-terraforming READMEgithub.comread
  2. cf-terraforming latest release (GitHub API)api.github.comread
  3. Terraform registry API, cloudflare/cloudflareregistry.terraform.ioread
  4. Cloudflare Terraform provider changeloggithub.comread
  5. Cloudflare Terraform provider (Cloudflare docs)developers.cloudflare.comread
  6. Import Cloudflare resourcesdevelopers.cloudflare.comread
  7. Terraform best practices (Cloudflare docs)developers.cloudflare.comread
  8. Create an API tokendevelopers.cloudflare.comread
  9. Version 5 migration guide (provider)registry.terraform.ioread via the provider repository source
  10. Migrating renamed resources (provider)registry.terraform.ioread via the provider repository source
  11. Manage sensitive data in your configuration (HashiCorp)developer.hashicorp.comread
  12. Dependency lock file (HashiCorp)developer.hashicorp.comread
  13. Generating configuration (HashiCorp)developer.hashicorp.comread
  14. Import a single resource (HashiCorp)developer.hashicorp.comread
  15. terraform plan command (HashiCorp)developer.hashicorp.comread
  16. Install Terraform (HashiCorp)developer.hashicorp.comread

About the author

Eduard Agopyan

Founder & Principal Architect, Saber Security · Sofia, Bulgaria

Eduard Agopyan is the founder and principal architect of Saber Security, a network, security, and cloud engineering company headquartered in Sofia, Bulgaria. Eduard has more than seven years of experience building and operating secure networks across on-premises and multi-cloud environments, and sets the architecture standards behind Saber Security’s work on networks, cloud connectivity, edge security, Zero Trust, and automation.

Need help with the work itself?

The articles describe the method. If you want our engineers to apply it to your environment, start with a few lines about what you run today.

    No results