1.0 Turf 1.0 is available now: autonomous infrastructure-as-code, built on OpenTofu. Free for noncommercial use. Read the announcement →

A new infrastructure-as-code engine · built on OpenTofu

One tool, from metal to workload, at fleet scale.

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.

$ brew install restatedev/tap/restate-server turfbuild/tap/turf
Claude Code, Cursor, Codex & any MCP client Terraform HCL & modules OpenTofu providers Local AI models and GPU clouds

the whole stack in one configuration — planned, approved and applied round by round

Not a layer over Terraform. A new engine, with superpowers.

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.

AGENTS · ANY MCP CLIENT Claude Code Cursor Codex Gemini CLI your platform MCP THE TURF CLI turf up · chat · destroy + turf engine durable · scale-out · interactive rounds: plan gate → plan → approval point → apply laptop or Kubernetes built on Restate providers unmodified modules from your registries state your backend

The engine. Agents reach it over MCP and the Turf CLI drives it directly; underneath are the same providers, modules and state backends.

ROUND 1 ROUND 2 CONVERGED planned approved applied approved kind_cluster.demo provider.helm helm · cert-manager manifest · app action · certify deferred paused for the agent plan apply deferred plan gate approval point turf action

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.

Layered stacks converge in rounds.

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.

main.tf · a cluster, then a chart each round planned & approved
Round 1 · create + kind_cluster.demo
# 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
}
Round 2 · create + helm_release.cert_manager
# 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
}
after turf-examples/terraform/kubernetes/kind-helm, the chart swapped for cert-manager as the ngc-stack example installs it

A round plans what it can know.

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.

  • ✓
    No -target, no staged applies — and no edits to the .tf: the file converges as written.
  • ✓
    An approval point on every round — each round’s plan is reviewed, by you or by policy, before it applies.
  • ✓
    A run that can’t progress says so — it ends stalled and names what it is waiting on.
  • ✓
    Teardown in reverse — turf destroy removes the release through the cluster before the cluster goes.
step 1

Declare intent

Natural language, a diagram, or Terraform HCL — desired state always lands in a reviewable configuration directory.

step 2 · the plan gate

Plan

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.

step 3 · the approval point

You approve

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.

step 4

Apply, with the agent overseeing

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.

step 5

Replan & converge

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

Resources and actions, in one plan.

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.

main.tf · a GPU hosta playbook as a Terraform action
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
  }
}
illustrative · after turf-examples/use-cases/datacenter/ansible-k3s · caller is the instance that fired the trigger
aicr.turf.hcl · an overlaya trigger added without touching the module
action_trigger "certify" {
  target     = module.stack.module.gpu_operator.helm_release.this
  events     = [after_create]
  actions    = [action.turf_action.certify]
  on_failure = taint
}

Resource lifecycle hooks.

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.

  • ✓
    Before and after create, update, destroy — with on_failure set to halt, continue or taint, and a plan-time condition.
  • ✓
    Overlays — a *.turf.hcl file adds a trigger with a target into any module, per environment or in memory for one run.
  • ✓
    Ad-hoc runs — invoke runs a declared action now, with its own plan gate, its own approval point and an audit record.
  • ✓
    Day 2 is the same plan, again — a variable changes, the triggers re-fire at the right points, under the same approval.

The agent is in the loop, not in charge.

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.

database.turf.hcl · an overlaya backup before, a word after
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
}
illustrative · the shape of turf-examples' installer.turf.hcl and maintenance.turf.hcl · caller is the instance that fired the trigger
  • ✓
    An approval point on every round — 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.
  • ✓
    Backups, checks, questions and notifications as actions — each a step of the apply, done by the agent with its own tools and recorded with the run.

Three ecosystems. Standard Terraform.

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.

Terraformproviders and modules
AWSGoogle CloudAzureKubernetesHelmCloudflareGitHubVaultDatadogvSphereProxmoxMAASRedfishterraform-aws-modulesAnsible provider … the whole registry
a module
module "eks" {
  source  = "terraform-aws-modules/eks/aws"
  version = "~> 21.0"
  name    = "gpu-west-1"
}
In Turf · as resources and modules, unchanged
Ansibleplaybooks and collections
ansible.builtincommunity.generalkubernetes.coreamazon.awsgoogle.cloudazure.azcollectionansible.posixcommunity.dockerdellemc.openmanageKubesprayk3s-ansible … your playbooks
a playbook, as an action
action "ansible_playbook_run" "gpu_node" {
  config {
    playbooks   = ["gpu-node.yml"]
    inventories = [data.ansible_inventory.gpu.json]
  }
}
In Turf · as a Terraform action: ansible-playbook, run from the plan
Helmcharts
cert-manageringress-nginxkube-prometheus-stackGrafanaCiliumexternal-dnsIstioVaultGPU OperatorNetwork OperatorNIM OperatorKubeRayKubeflowkagent … any chart
a chart, as a resource
resource "helm_release" "gpu_operator" {
  repository = "https://helm.ngc.nvidia.com/nvidia"
  chart      = "gpu-operator"
  version    = "v26.7.1"
}
In Turf · as a Terraform resource: synced as a true Helm release, hooks included

Start anywhere. It’s all standard Terraform.

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.

Radically honest compatibility. Where a construct isn’t supported yet, Turf refuses loudly — it never silently mis-plans. Every gap is published and labeled coming soon on the matrix.
  • ✓
    Full Terraform HCL — every expression and function, no allow list.
  • ✓
    Module registry — public, private or git, installed and locked by init.
  • ✓
    OpenTofu providers — the whole provider ecosystem, unmodified.
  • ✓
    Charts, synced — every helm_release is a true Helm release: installed and upgraded through Helm, hooks included, visible to helm list.
  • ✓
    From the console to configuration — the agent writes the HCL; an import block brings what exists under management, reviewed as a real diff.
  • ✓
    Terraform actions, with overlays — the 1.16 action model, plus triggers added per environment.ahead of OpenTofu
  • ✓
    Multi-environment deploys — one configuration, a workspace per environment, promoted with overlays.

Your ops environment, Turf’s engine.

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.

+ turf engine plan approve apply log laptop or Kubernetes GIT REPOS The configuration GitHub · GitLab · any remote pull request → review → merge HCL REGISTRIES Modules and providers Artifactory · the public registries installed by init, locked SECRETS The central store a vault · a cloud secrets manager ephemeral · write-only · never in state POLICY Checks every plan OPA · conftest · your policy repo or your platform's policy groups plan STATE Your backend S3 · GCS · Azure Blob · in the cluster or a remote backend · encrypted AGENTS · THE CONTROL PLANE Over MCP Claude Code · Cursor · your portal open · up · plan · apply · output MCP YOUR PLATFORM Scalr · Spacelift · over MCP

The engine in the middle. Git repos, registries, secrets, policy and state on their own lanes; agents and your platform over MCP.

versions.tf · db.tfyour backend, your registry, your secrets store
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
}
illustrative · backends, registries and ephemeral values are standard Terraform; Turf runs them as written

Standard Terraform, all the way down.

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.

  • ✓
    Policy on every plan — the plan exports as the document OPA or conftest reads, or goes to your platform’s policy groups, Scalr or Spacelift, through its own MCP server.
  • ✓
    Secrets never in state — an ephemeral value feeds a write-only argument; never in the plan, the state or the directory.
  • ✓
    State where you keep it — S3, GCS, Azure Blob, a secret in the cluster, or a platform’s remote backend; encrypted at rest.
  • ✓
    Modules from your registries — Artifactory or the public registries, installed and locked by init.

Built to run the whole stack.

One engine for every stack on this page, on a laptop or as a service in the cluster.

THE TURF ENGINE replica 1 refresh · plan · apply, per resource resource 1 resource 2 resource 3 replica 2 refresh · plan · apply, per resource resource 1 resource 2 resource 3 replica 3 refresh · plan · apply, per resource resource 1 resource 2 resource 3 read plan create · update · delete FLOW CONTROL per provider · its own quota COMING SOON AWS API · US-EAST-1 The region's API provider "aws" { alias = "east" } one instance · its own pace FLOW CONTROL per provider · its own quota COMING SOON AWS API · EU-WEST-1 The region's API provider "aws" { alias = "west" } one instance · its own pace ONE PROVIDER INSTANCE PER REGION, ACCOUNT OR ALIAS Every call fans out. Each provider keeps its own pace.

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.

Every provider call scales out. The cloud API is the ceiling.

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.

Durable, observable, yours to run.

  • Durable runs. Every step is recorded as it happens; an interrupted run picks up where it stopped, a plan waiting for approval holds no process, and a rollout outlives the conversation that started it.
  • Laptop or Kubernetes®. Runs on a workstation, or as a service in the cluster.
  • OpenTelemetry built in. Every run traced as it happens, exported over OTLP; metrics in Prometheus format.
  • Encrypted at rest. State encryption with a passphrase or a key.
  • Operator console. Every stack, run and approval in one place. coming soon

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.

versions.tfstate encrypted at rest
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
    }
  }
}

Encrypted at rest, enforced.

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.

Five things people build with Turf.

From a GPU cloud to a pull request that applies itself: the same engine, the same plan, the same plan gates and approval points.

Neocloud providers

GPU clouds, from metal to tokens

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 →
SaaS and platform providers

Managed services on Kubernetes fleets

A control plane, a golden recipe, and a fleet of tenant clusters, each its own stack under approval.

one recipe, many clusters →
ISVs and AI-native products

Products that install themselves

The reference architecture as modules, planned in the customer’s account, with the installer’s own steps as turf actions.

ship the installer →
Teams adopting agents

The ultimate infrastructure agent

Skills, state tools, provider schemas and the plan for policy, designed for efficient reasoning models.

the agent’s toolkit →
Platform engineers

Pull request to production

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 →

Runs where you work. On any model — even yours.

Interfaces

One engine, many surfaces — connect your agent in one command.

Any MCP clientClaude Code & Desktop, Cursor, Codex, Gemini CLI, VS Code — stdio or HTTP
supported
CLI & TUIthe standalone Turf agent: chat, up, destroy — bring your own model
supported
Kubernetes, in-clusterdeployed as an agent via kagent, state in the cluster
supported

Models

The standalone agent runs on any model; your MCP client brings its own.

Hosted frontier modelsAnthropic, Google, and OpenAI-compatible endpoints
supported
Local modelsturf --model dmr/ai/qwen3 — no API key, no cost
supported
Private GPU cloudsvLLM, LM Studio, gateways via --base-url
supported

Your infrastructure intent never has to leave your network.

Evaluating Turf at a company?

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.

Start a pilot or read the terms at turf.build/licensing