# Tofu Controller reconciles OpenTofu and Terraform resources from Git

> Tofu Controller, the renamed Weave TF-Controller, is a Flux controller that applies OpenTofu and Terraform resources in the GitOps way, with four models ranging from full automation to drift detection only. It suits Kubernetes operators who want infrastructure reconciled from Git, and it is Kubernetes-only.

**flux-iac/tofu-controller** — A GitOps OpenTofu and Terraform controller for Flux

- Repository: https://github.com/flux-iac/tofu-controller
- Website: https://flux-iac.github.io/tofu-controller/
- Stars: 1,722 · Forks: 201
- Language: Go
- License: Apache-2.0
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/flux-iac-tofu-controller

## The four models decide how much of your infrastructure the controller owns

Tofu Controller, previously known as Weave TF-Controller, is a controller for Flux to reconcile OpenTofu and Terraform resources in the GitOps way. The project states its aim plainly: with Flux together with OpenTofu and Terraform, it allows you to GitOps-ify infrastructure and application resources in the Kubernetes and IAC universe, at your own pace.

That phrase at your own pace is operationalised in four models, and choosing between them is the first decision an adopter has to make.

The GitOps Automation Model is the full one. You GitOps your OpenTofu and Terraform resources from the provision steps to the enforcement steps, like a whole EKS cluster. The controller creates resources and then keeps them in the desired state.

The Hybrid GitOps Automation Model GitOps parts of your existing infrastructure resources. The example given is an existing EKS cluster where you GitOps only its nodegroup, or its security group. This is the model for organisations that cannot hand over their whole estate at once, and it is arguably the most useful one in practice because the scope of a change is bounded and reviewable.

The State Enforcement Model assumes you have a TFSTATE file and want to use GitOps to enforce it, without changing anything else. Here the controller is not authoring infrastructure, it is making the real world match an existing state file.

The Drift Detection Model uses a TFSTATE file only to detect drift, so that you can decide what to do later when drift occurs. This is the read-mostly model, and the one to consider if you are not ready to let a controller change anything yet.

Having all four means the tool can be introduced without committing to full automation, which is the difference between a project people try and one they avoid.

## A Go controller built on the Flux runtime and the Terraform exec library

The technical foundation is visible in the go.mod, and it explains how the controller works.

The Flux side comes from fluxcd/pkg/runtime at 0.111.0 and fluxcd/pkg/apis/meta, plus fluxcd/source-controller/api at 1.9.4 for the source that provides the Git content. The Kubernetes libraries are k8s.io/api and k8s.io/apiextensions-apiserver at 0.37.0, so this is a custom resource controller in the standard kubebuilder style, with api/, controllers/ and internal/ directories at the top of the repository.

The Terraform side is hashicorp/terraform-exec at 0.25.3, which is the library that runs a Terraform or OpenTofu binary and parses its output, together with hashicorp/terraform-json at 0.28.0 for the plan and state JSON format, hashicorp/go-cleanhttp and hashicorp/go-retryablehttp for the HTTP layer, and hashicorp/hcl2 for HCL parsing. The controller therefore drives the real tool rather than reimplementing it, which is the right approach given how much behaviour lives in the plan and apply cycle.

The module targets Go 1.26.6 and replaces the api and tfctl modules with local paths, so those two are developed in the same repository rather than pulled from a release. The tfctl/ directory is where that command line client lives.

The rest of the dependency list is infrastructure. The AWS SDK v2 with s3 and dynamodb covers storage backends and state handling, bradleyfalzon/ghinstallation covers GitHub App authentication, jenkins-x/go-scm and kubescape/go-git-url handle source providers beyond plain Git, cyphar/filepath-securejoin guards path handling, spf13/cobra and viper build the command line, and Masterminds/sprig provides the template functions used when rendering Terraform from Flux templates. counterfeiter generates the test fakes, and gomega and testify carry the test suite.

## The controller, the runner and the planner are separate images

The repository builds more than one container, and the split is the interesting part of the architecture.

The Makefile names four image targets. MANAGER_IMG is ghcr.io/flux-iac/tofu-controller, RUNNER_IMG is ghcr.io/flux-iac/tf-runner, and RUNNER_AZURE_IMAGE is ghcr.io/flux-iac/tf-runner-azure, plus BRANCH_PLANNER_IMAGE at ghcr.io/flux-iac/branch-planner.

The controller Dockerfile is a good illustration of the pattern. It builds the manager binary from cmd/manager with CGO_ENABLED=0, using ldflags to stamp in the build SHA and version, and then ships it in a scratch image with only the CA certificates and a passwd file copied in. The image runs as UID 65532 with a non-login shell, and the entrypoint is the binary itself.

So the controller is a small, hardened, dependency-free image. The runner is a separate image because the work of actually executing OpenTofu needs a different set of tools, and the runner variants exist because different Git providers need different credentials, hence the Azure-specific runner and the branch-planner image for previewing changes per branch.

The Dockerfile copies only the folders it needs, api/, cmd/, controllers/, internal/, mtls/, runner/, tfctl/ and utils/, then runs go mod download separately so dependency caching works. The mtls/ directory suggests mutual TLS material is part of the design, which fits a controller that talks to in-cluster runners over the API server.

The local development story is a Tiltfile, a local-e2e.sh script, a planner.Dockerfile and planner.Dockerfile.dev alongside runner.Dockerfile and runner.Dockerfile.dev, and a Dockerfile.dev. Tilt is the standard tool for a live-update loop across several images, and having it in the repository is a sign the project expects frequent iteration on the controller, the runner and the planner at the same time.

## The Helm chart and the CRD layout follow the Flux conventions

The repository root shows the standard layout of a Flux-adjacent Kubernetes controller, and reading it tells you what the project's surface area is.

There is a charts/ directory for the Helm chart, a PROJECT file which is the kubebuilder project descriptor, a config/ directory for the standard Kubernetes manifests including the CRDs and RBAC, an api/ directory for the Go types of the custom resource, controllers/ for the reconcilers, internal/ for the implementation, and cmd/ for the manager entry point. There is a ct.yaml, which is the chart-testing configuration used by the Helm chart CI, and a mkdocs.yml with a docs/ directory for the documentation site.

The presence of a planner image alongside the controller and runner, plus the branch-planner naming, points at a design where a plan can be computed for a branch and reviewed before it is applied. That is a meaningful difference from a controller that simply applies what is in the default branch, and it is the sort of thing that changes how a platform team runs it, because a human approval step can sit in front of an apply.

The ADOPTERS.md and rfcs/ directory are worth noting for anyone wanting to understand the design decisions rather than just the usage. The rfcs/ directory holds design documents, which is the mechanism a project of this kind uses to record why a behaviour exists. There is a DEVELOPMENT.md, a RELEASE_PROCESS.md, a CHANGELOG.md, a SECURITY.md, a DCO file, a MAINTAINERS file and a lintconf.yaml.

The docs site is at flux-iac.github.io/tofu-controller, and the README points to a getting started guide for GitOps-ing Terraform resources with Tofu Controller and Flux, plus a use cases page that covers the models in more depth.

## Apache-2.0, a rename, and a 0.x line that moves regularly

The licence is Apache-2.0, with the LICENSE file in the repository as the authoritative text, and this is a licensing statement rather than legal advice.

The recent releases are v0.16.5 on 2026-08-06, v0.16.4 on 2026-06-08 and v0.16.3 on 2026-05-05. The repository is not archived and the last push was on 2026-10-12, so the work is current. The patch versions come roughly every two months, with a minor bump when behaviour changes.

The rename is the more interesting fact. Tofu Controller was previously known as Weave TF-Controller, and the project now sits under the flux-iac organisation rather than a vendor. That is a governance signal: a controller for Flux maintained by the Flux-adjacent community, with the OpenTofu name rather than the Terraform name in the title, and the project homepage at flux-iac.github.io.

The 0.x version number is the usual signal that the API is not yet frozen, and for a project with custom resources that means the CRD schema can still change between minor versions. If you are building on it, the practical advice is to pin a version, read the CHANGELOG.md before bumping, and treat the CRD as part of your contract.

The build tooling is well specified. The Makefile drives a .goreleaser.yml for releases, uses a BUILD_SHA from git and a BUILD_VERSION from git describe, pins a source controller version for the tests, and sets TARGETARCH for the cross-compiled binaries including the OpenTofu binary bundled into the images. The CI setup includes a Tiltfile for local development, a local-e2e.sh script for end-to-end runs, and a .trivyignore, which is the file you would expect in a project that ships container images and wants them scanned.

## Conclusion

Adopt Tofu Controller if you run Flux on Kubernetes and want infrastructure declared in Git and reconciled by a controller, and if you can accept that OpenTofu runs inside the cluster rather than on a workstation. Do not adopt it if you want OpenTofu to run locally as part of a developer workflow, since the runner executes inside the cluster. Before you start, pick the model deliberately, because the four models differ in what they own, and note that the state enforcement and drift detection models need an existing TFSTATE file to be useful at all.

## FAQ

### What is Tofu Controller and what does it do?

It is a controller for Flux that reconciles OpenTofu and Terraform resources in the GitOps way, previously known as Weave TF-Controller. It lets you GitOps infrastructure and application resources in the Kubernetes and IaC universe, at your own pace, meaning you do not need to GitOps everything at once.

### What are the GitOps models Tofu Controller supports?

Four: the GitOps Automation Model, which provisions and enforces resources from the provision steps to the enforcement steps; the Hybrid GitOps Automation Model, which GitOps parts of existing infrastructure such as one nodegroup or security group; the State Enforcement Model, which enforces an existing TFSTATE file; and the Drift Detection Model, which uses a TFSTATE file to detect drift without acting on it.

### How does Tofu Controller run OpenTofu and Terraform?

The go.mod includes hashicorp/terraform-exec, hashicorp/terraform-json and hashicorp/hcl2, and the repository has runner.Dockerfile and runner-azure.Dockerfile at the top level plus a planner image at ghcr.io/flux-iac/branch-planner, so the tool runs in a separate runner image rather than inside the controller.

### What container images does the project publish?

The Makefile names ghcr.io/flux-iac/tofu-controller for the manager, ghcr.io/flux-iac/tf-runner for the runner, ghcr.io/flux-iac/tf-runner-azure for the Azure variant and ghcr.io/flux-iac/branch-planner for the planner. The controller image is built from scratch with only CA certificates and a passwd file, running as UID 65532.

### Is Tofu Controller maintained and under what licence?

The repository is not archived, the last push was on 2026-10-12, and recent releases are v0.16.5 on 2026-08-06, v0.16.4 on 2026-06-08 and v0.16.3 on 2026-05-05. The licence is Apache-2.0 with the LICENSE file in the repository as the authoritative text.

## Sources

- [flux-iac/tofu-controller on GitHub](https://github.com/flux-iac/tofu-controller)
- [License: Apache-2.0](https://github.com/flux-iac/tofu-controller/blob/main/LICENSE)
- [Project website](https://flux-iac.github.io/tofu-controller/)
- [README](https://github.com/flux-iac/tofu-controller/blob/main/README.md)
- [Releases](https://github.com/flux-iac/tofu-controller/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/flux-iac-tofu-controller
