A new infrastructure-as-code engine · built on OpenTofu
Turf™ is a new engine for infrastructure as code. Describe the whole stack in the Terraform® language — hosts, clusters, charts, playbooks, the workload — and plan every operation the same way: resources and actions, converged in rounds, in one run. Agents oversee it from Claude Code, Cursor, Codex or any MCP client. Every change is planned, visible, and approved before it happens. Nothing runs off-plan.
pools of inventory · the provider'saction "ansible_playbook_run" "gpu_node"module "eks"module "runtime"resource "kubernetes_manifest" "certification"resource "kubernetes_manifest" "model"✓ plan · 2 rounds → converged
the whole stack in one configuration — planned, approved and applied round by round
Most automation platforms and agent tools shell out to terraform or tofu at some point. Turf replaces that tool with a durable, scale-out, interactive engine. What you write is standard Terraform, whether you have any yet or not.
The engine. Agents reach it over MCP and the Turf CLI drives it directly; underneath are the same providers, modules and state backends.
One run. Each round opens at its plan gate. Round 1 plans, approves and applies the cluster and defers the chart; round 2 configures the Helm provider from the cluster’s endpoint and finishes the chart and the manifest, pausing once for the agent at a turf action.
A cluster, then the chart that runs on it. One configuration, one turf up: Turf
plans what it can know, applies it, and plans again, with a plan gate and an approval point
every round.
# A local Kubernetes cluster, as Docker containers. resource "kind_cluster" "demo" { name = "${var.cluster_name}-${var.generation}" node_image = var.node_image wait_for_ready = true }
# The helm provider binds to the cluster's endpoint, unknown # until round 1 applied. Turf configures it here, then plans # the release. provider "helm" { kubernetes = { host = kind_cluster.demo.endpoint client_certificate = kind_cluster.demo.client_certificate client_key = kind_cluster.demo.client_key cluster_ca_certificate = kind_cluster.demo.cluster_ca_certificate } } resource "helm_release" "cert_manager" { name = "cert-manager" repository = "https://charts.jetstack.io" chart = "cert-manager" version = "v1.21.2" namespace = "cert-manager" create_namespace = true set = [{ name = "crds.enabled", value = "true" }] wait = true }
Round 1 creates the cluster. The helm provider reads the cluster’s endpoint, unknown until then, so Turf defers the release instead of failing, applies what it can, and plans again.
Round 2 configures the provider from the applied endpoint and syncs the release
through Helm itself: a true Helm release, CRDs and hooks included, that helm list
sees. Nothing is left deferred, so the run ends: converged, from empty, in one command.
-target, no staged applies — and no edits to
the .tf: the file converges as written.stalled and names what it is waiting on.turf destroy removes the
release through the cluster before the cluster goes.Natural language, a diagram, or Terraform HCL — desired state always lands in a reviewable configuration directory.
Each round waits at its plan gate until plan is called, with this round’s
refresh and replace choices. Turf produces a standard Terraform plan of the whole directory:
resources, actions, and what this round defers.
People and policy checks sign off on the plan: apply with its digest approves
it, apply with decline turns it down. Nothing is applied until this
plan, and no other, is approved.
The engine applies the approved plan. A turf action stops it for a check, a backup or a question — the agent, and you, step in there.
Deferred work — the provider that couldn’t be configured yet — loops back to the next round’s plan gate until the graph settles.
↻ step 5 feeds back into step 2 — every round stops at its plan gate and its approval point again
Desired state, and the steps between it: a playbook run once the hosts exist, a wait for a condition, a certification. Both are written in the Terraform language, both are planned, and both cross the approval point.
resource "aws_instance" "gpu_host" { ami = data.aws_ami.ubuntu.id instance_type = "g6.xlarge" subnet_id = aws_subnet.public.id key_name = aws_key_pair.ops.key_name lifecycle { action_trigger { events = [after_create] actions = [action.ansible_playbook_run.host] on_failure = taint } } } action "ansible_playbook_run" "host" { config { playbooks = ["playbooks/gpu-host.yml"] inventory_files = ["${caller.public_ip},"] user = "ubuntu" private_key_file = var.private_key_file } }
caller is the instance that fired the triggeraction_trigger "certify" { target = module.stack.module.gpu_operator.helm_release.this events = [after_create] actions = [action.turf_action.certify] on_failure = taint }
Terraform 1.16 actions, carried by Turf: a provider-defined operation hooked to one of six points in a resource’s lifecycle, planned beside the resource and applied inside the same run. OpenTofu has no action blocks.
on_failure set to halt, continue or taint, and a plan-time condition.*.turf.hcl file adds a trigger with a
target into any module, per environment or in memory for one run.invoke runs a declared action now,
with its own plan gate, its own approval point and an audit record.A turf action is a step the configuration asks the agent to carry out during the apply: a backup, a check, a deployment step, a question, a notification. The apply stops for it, the agent does the step with its own tools and reports back, and the configuration says what a failure means.
action "turf_action" "backup" { config { prompt = <<-EOT Snapshot ${caller.identifier} with the AWS CLI or the AWS MCP tools, wait until it is available, and report its identifier in progress. If it does not complete, fail: the database stays as it is. EOT inputs = { db = caller.identifier, region = var.region } } } action_trigger "backup" { target = aws_db_instance.primary events = [before_update, before_destroy] actions = [action.turf_action.backup] on_failure = halt } action "turf_action" "notify" { config { prompt = <<-EOT Tell the ops channel that ${caller.identifier} is now on ${caller.engine} ${caller.engine_version}, with your messaging tool; without one, fail and say so. EOT } } action_trigger "notify" { target = aws_db_instance.primary events = [after_update] actions = [action.turf_action.notify] on_failure = continue }
plan plans the
round, and you or policy approve it: apply with its digest is the approval, of that
plan and no other; apply with decline turns it down.TOOL RESULT · TURF MCPapply → awaiting_actions action_requests = [{ address = "action.turf_action.backup" event = "before_update" caller = "aws_db_instance.primary" inputs = { db = "orders-prod", region = "us-east-1" } }] paused · the database untouched
Before orders-prod moves to PostgreSQL 17.6: a snapshot first.
aws rds create-db-snapshot orders-prod-before-17-6aws rds wait db-snapshot-available availableSnapshot reported in progress. The apply continues.
→ apply(action_results): succeeded · the update runs · after_update: awaiting_actions
Told the team in #ops: orders-prod is on PostgreSQL 17.6. Snapshot orders-prod-before-17-6 is kept.
→ apply(action_results): succeeded · converged
Nothing in a stack is written from nothing. Terraform providers and modules, Ansible® playbooks and Helm® charts already cover it, from the hardware to the workload, and Turf runs each as it is.
module "eks" { source = "terraform-aws-modules/eks/aws" version = "~> 21.0" name = "gpu-west-1" }
action "ansible_playbook_run" "gpu_node" { config { playbooks = ["gpu-node.yml"] inventories = [data.ansible_inventory.gpu.json] } }
resource "helm_release" "gpu_operator" { repository = "https://helm.ngc.nvidia.com/nvidia" chart = "gpu-operator" version = "v26.7.1" }
Clicked it together in a console? Syncing charts with a GitOps controller today? Turf runs the Terraform language on the OpenTofu framework: the whole expression language, the function library, registry modules and the provider ecosystem, as they are. Turf is an independent product built on the OpenTofu framework, not affiliated with or endorsed by HashiCorp.
init.helm_release is a true Helm
release: installed and upgraded through Helm, hooks included, visible to helm list.import block brings what exists under management, reviewed as a real diff.Plug the engine into what your platform team already runs: configuration from your git repos, modules and providers from your registries, secrets from your central store, policy from wherever you keep it, state in your object storage or remote backend.
The engine in the middle. Git repos, registries, secrets, policy and state on their own lanes; agents and your platform over MCP.
terraform { # or "remote" for a platform, "kubernetes" in the cluster backend "s3" { bucket = "platform-state" key = "payments/network.tfstate" } } module "network" { # a private registry: Artifactory, or your own source = "registry.example.com/platform/network/aws" version = "~> 3.0" } ephemeral "vault_kv_secret_v2" "db" { mount = "kv" name = "payments/db" } resource "aws_db_instance" "payments" { # write-only: the secret never reaches state password_wo = ephemeral.vault_kv_secret_v2.db.data.password }
A backend block says where state lives: object storage, a secret in the cluster,
or a platform’s remote backend, and an encryption block keeps it encrypted at
rest. A module comes from any registry, locked by init. An
ephemeral value reaches a resource only through a write-only argument, so the
secret is never in the plan, the state or the directory. Nothing here is Turf-specific; the
engine underneath is what is new.
init.One engine for every stack on this page, on a laptop or as a service in the cluster.
Shipped: every refresh, plan and apply call fans out from one engine, or from several replicas of it, with a provider instance per region, account or alias. coming soon per-provider flow control, to reach each API’s quota without tripping it.
Refresh reads every managed resource on every plan, and in one process it is the classic ceiling on a large stack.
Turf fans refresh, plan and apply out per resource across the engine, with durable per-resource state instead of one statefile under one lock. Thousands of resources per stack; the cloud API, not the tool, sets the pace. Per-provider flow control coming soon paces each provider to its own quota.
The engine runs on Restate, in Restate’s words “a lightweight runtime to turn AI agents, workflows, and backend services into durable processes.” How the engine is built →
coming soon A preview of the Turf console: every stack, run and approval in one place. Click to enlarge.
A preview of the Turf console: every stack, run and approval in one place. Click to enlarge.
terraform { encryption { key_provider "pbkdf2" "k" { passphrase = var.passphrase } method "aes_gcm" "m" { keys = key_provider.pbkdf2.k } state { method = method.aes_gcm.m enforced = true } } }
An encryption block protects the statefile with a passphrase or a key provider;
migrating an existing state is a no-op run, and enforced refuses plaintext. A
secret a resource needs goes through a write-only argument and never reaches state at all.
From a GPU cloud to a pull request that applies itself: the same engine, the same plan, the same plan gates and approval points.
Pools of inventory to a certified cluster and a served model, in one run, and day 2 on the provider’s runbook.
metal to tokens →A control plane, a golden recipe, and a fleet of tenant clusters, each its own stack under approval.
one recipe, many clusters →The reference architecture as modules, planned in the customer’s account, with the installer’s own steps as turf actions.
ship the installer →Skills, state tools, provider schemas and the plan for policy, designed for efficient reasoning models.
the agent’s toolkit →An agent in the pipeline, on GitHub Agentic Workflows: the plan judged by your policy on every pull request, applied as that exact plan, verified from inside the apply.
the plan, reviewed every time →NVIDIA, AI Cluster Runtime, NVCRE, NVSentinel, KAI Scheduler and NIM are trademarks or product names of NVIDIA Corporation, referred to here only to describe interoperability. Amazon EKS, Google Kubernetes Engine and Azure Kubernetes Service are trademarks of their respective owners. MAAS is a trademark of Canonical Ltd. Redfish is a trademark of DMTF. Amazon Web Services and AWS are trademarks of Amazon.com, Inc. or its affiliates. GitHub and GitHub Agentic Workflows are trademarks of GitHub, Inc. Restate is a product of Restate Software, Inc. and Restate GmbH, named here to describe what Turf is built on. Turf is not affiliated with, endorsed by, or sponsored by any of them. All other marks belong to their owners.
One engine, many surfaces — connect your agent in one command.
chat, up, destroy — bring your own modelThe standalone agent runs on any model; your MCP client brings its own.
turf --model dmr/ai/qwen3 — no API key, no cost--base-urlYour infrastructure intent never has to leave your network.
Commercial use, including a company’s internal use, requires a license. Companies evaluate Turf under a free 60-day pilot on a standard contract you can read in full: the same binary everyone downloads, a direct support channel for the pilot, and no license key. The teams that pilot early shape what comes next.