# TFLint: a pluggable Terraform linter whose real work happens in plugins

> TFLint ships a bundled Terraform-language ruleset and pulls provider-specific checks from separate plugins. This review covers how the plugin mechanism works, how to install it on Linux, macOS and Windows, and where the design leaves you on your own.

**terraform-linters/tflint** — A Pluggable Terraform Linter

- Repository: https://github.com/terraform-linters/tflint
- Stars: 5,828 · Forks: 412
- Language: Go
- License: MPL-2.0
- Published: 2026-09-22 · Updated: 2026-09-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/terraform-linters-tflint

## What TFLint catches that terraform validate does not

Terraform's own validate command checks that a configuration is internally consistent. It does not know that an instance type does not exist in the region you are deploying to, or that a declaration is now deprecated. TFLint exists to fill that gap. The README lists three feature groups: finding possible errors such as invalid instance types for major cloud providers, warning about deprecated syntax and unused declarations, and enforcing best practices and naming conventions.

The audience is teams that already have Terraform running and want a second pass before apply. The provider-specific checks are the reason to care. A typo in an AWS instance type is not a syntax error, so it survives validate and fails at apply time, sometimes after a long plan. TFLint's AWS, Azure and Google rulesets are separate plugins, which means the depth of checking depends on which ones you install.

One consequence of the plugin design is worth stating plainly: a fresh TFLint install without provider plugins is mostly a Terraform-language linter. The bundled ruleset covers syntax-level and convention-level rules, not cloud provider knowledge.

## The plugin mechanism: a framework with a bundled ruleset

The README describes TFLint as "a framework and each feature is provided by plugins". That is the architecture in one line. The binary itself is the host; rules live in plugins that are declared in a config file and installed on demand.

Two details in the repository confirm how the pieces fit. The go.mod file requires github.com/hashicorp/go-plugin and github.com/terraform-linters/tflint-plugin-sdk, so plugins are separate processes speaking to the host over HashiCorp's plugin protocol rather than compiled-in Go code. The same go.mod requires github.com/terraform-linters/tflint-ruleset-terraform, which is the ruleset the README says is bundled, so you get Terraform-language rules without a separate download.

The top-level layout backs this up. There is a plugin/ directory (which the Makefile touches through plugin/stub-generator), a terraform/ directory, a langserver/ directory, and a formatter/ directory. Plugins are installed by name, version and source, and the README points to the tflint-ruleset topic on GitHub as the way to find third-party rulesets. If existing plugins do not cover a rule you need, the README offers two extension paths: write a plugin, or write a policy in Rego through the OPA ruleset.

## Installing TFLint on Linux, macOS and Windows

The README gives four installation routes. On Linux, the release archive is downloaded, its checksums file is verified with the GitHub CLI, and the binary is installed to /usr/local/bin. The commands below are copied from the README; the attestation step is what ties the checksums file to a build from this repository.

```bash
curl -sSLO https://github.com/terraform-linters/tflint/releases/latest/download/tflint_linux_amd64.zip
curl -sSLO https://github.com/terraform-linters/tflint/releases/latest/download/checksums.txt
gh attestation verify checksums.txt -R terraform-linters/tflint
sha256sum --ignore-missing -c checksums.txt
unzip tflint_linux_amd64.zip
sudo install -c -v tflint /usr/local/bin/
```

The README marks the older Cosign verification path as deprecated and tells readers to use the GitHub CLI instead. If you installed with Cosign previously, that is the change to make.

On macOS the README points at Homebrew, and on Windows at WinGet. Both are one-liners, and neither does the manual checksum step.

```bash
brew install terraform-linters/tap/tflint
```

```bash
winget install -e --id TerraformLinters.tflint
```

If you have a Go toolchain, the README also allows installing from source. Note that the module is built against the Go version in go.mod, which is 1.26.5.

```bash
go install github.com/terraform-linters/tflint@latest
```

For a container-based run, the README shows a Docker invocation that mounts the working directory at /data, which is also the WORKDIR set in the Dockerfile. The second form overrides the entrypoint so that plugin installation and the lint run happen in one container.

```bash
docker run --rm -v $(pwd):/data -t ghcr.io/terraform-linters/tflint
docker run --rm -v $(pwd):/data -t --entrypoint /bin/sh ghcr.io/terraform-linters/tflint -c "tflint --init && tflint"
```

## A first .tflint.hcl and the plugin install step

TFLint reads .tflint.hcl from the current directory unless you pass --config=FILE. The README's getting-started section starts with the bundled Terraform-language ruleset, which is enabled by default with the recommended preset. Declaring it explicitly is how you change the preset or turn it off.

```hcl
plugin "terraform" {
  enabled = true
  preset  = "recommended"
}
```

Provider plugins follow the same shape. The README's example uses a placeholder plugin named foo with a version and a GitHub source, and states that tflint --init installs plugins declared this way.

```hcl
plugin "foo" {
  enabled = true
  version = "0.1.0"
  source  = "github.com/org/tflint-ruleset-foo"
}
```

After writing the config, run the init command once to fetch plugins, then run TFLint. The README states that TFLint inspects files under the current directory by default, and that --recursive runs the command in each directory, while --chdir=DIR switches the working directory first.

```bash
tflint --init
tflint --recursive
```

Output defaults to the human-readable format. The --help text lists json, checkstyle, junit, compact and sarif as alternatives, which is what you wire into a CI job that already parses one of those. There is also a --langserver flag, and the repository has a langserver/ directory, which is the path an editor integration would use; the README does not document an editor setup beyond that.

## Where TFLint stops being the right tool

The plugin model has a cost. Every ruleset beyond the bundled one is a separate artifact with its own version, and the README pins plugin versions inside .tflint.hcl. That means an upgrade is two decisions: the TFLint binary and each plugin version. Nothing in the README describes an automatic compatibility check between a plugin version and the host version, so a plugin pinned to an old release is a thing you maintain on purpose.

Module handling is the second boundary. The --call-module-type option accepts all, local or none and defaults to local. Local means TFLint inspects modules referenced from the local filesystem. If your configuration calls modules from a registry or a Git source, the default will not walk into them, and the README does not promise that changing the flag to all makes every remote module resolvable. There is also --ignore-module=SOURCE for the opposite case.

The third limit is scope. TFLint is a linter. It reads configuration and reports issues; it does not plan, does not talk to a cloud API to check whether an instance type is available in your account, and does not replace a policy engine that evaluates planned resources. The README's own framing, finding "possible errors", is honest about that. If your requirement is compliance evidence over what Terraform intends to create, a linter that never sees a plan is the wrong layer.

Finally, the README does not document rollback of a plugin install or a way to audit which plugin binary was fetched beyond the version and source fields. For a tool that runs in CI with credentials in the environment, that gap is worth knowing about before you enable third-party rulesets.

## TFLint versus Checkov and OPA-style policy tools

The closest comparison in the search data is Checkov, and the difference is in what each one reads. TFLint parses HCL through hashicorp/hcl/v2 and evaluates it with rulesets that understand Terraform semantics, including provider-specific argument values. Its bundled ruleset is about the Terraform language itself: deprecated syntax, unused declarations, naming conventions.

Checkov-style scanners work from a catalogue of policy checks, often expressed against resource attributes, and are typically aimed at security and compliance posture rather than language correctness. The overlap is real: both will flag a resource that looks wrong. The difference is that TFLint's provider plugins carry knowledge of specific cloud APIs (the README's example is invalid instance types), while a policy scanner carries a broader set of security-oriented checks that are not tied to Terraform's own grammar.

TFLint also offers an OPA path, the tflint-ruleset-opa plugin, which lets you write policy in Rego. That is the middle ground: you keep TFLint's parsing and issue reporting, and you write the rule yourself. If your team already writes Rego for other tools, that is the cheaper extension route than authoring a Go plugin against tflint-plugin-sdk.

The practical split: use TFLint for Terraform correctness and provider argument validation, and keep a separate policy scanner if you need security controls evaluated across resource types. Running both is common, and the SARIF output format exists so the two sets of findings can land in the same code-scanning view.

## Maintenance, licence and what an upgrade actually costs

The repository is not archived, and the last push was on 2026-09-19. Releases are frequent enough to plan around: v0.64.0 on 2026-07-17, v0.63.1 on 2026-06-03, and v0.63.0 on 2026-06-02. If you pin plugin versions in .tflint.hcl, budget time for the plugin side of each upgrade, not just the binary.

Building from source has its own steps. The Makefile's default target is build, which creates dist/ and runs go build -o dist/tflint. The test target depends on prepare, which initialises git submodules and runs the stub generator before running go test. That means a source checkout is not a plain go build; the submodule init is required, and the repository has a .gitmodules file. The Dockerfile builds the same way, running make build in a golang builder stage and copying dist/tflint into an Alpine runtime image.

Licensing needs care rather than a summary. The README's badge reads "License: MPL 2.0 + BUSL 1.1", and the repository root contains both LICENSE and LICENSE-BUSL. The metadata for the project lists MPL-2.0. The README does not explain which files fall under which licence, so if you redistribute TFLint or embed it in a product, read both files in the repository and get your own advice. This article is not legal advice and cannot tell you which terms apply to your use.

## Conclusion

Adopt TFLint if you already run terraform validate in CI and want provider-aware checks such as invalid instance types, plus a ruleset you can extend with your own plugin or Rego policy. Do not adopt it expecting a security scanner: TFLint is a linter, and the README frames its features as finding possible errors, flagging deprecated syntax and unused declarations, and enforcing naming conventions. Before rolling it out, verify three things: that the plugin versions you pin in .tflint.hcl resolve with tflint --init, that --call-module-type matches how your modules are referenced (the default is local, so remote module calls are not inspected unless you change it), and which licence file applies to the components you redistribute, because the repository carries both LICENSE (MPL-2.0) and LICENSE-BUSL.

## FAQ

### What are the key differences between Terraform Validate and TFLint?

Terraform's validate command checks that a configuration is internally consistent, while TFLint is a separate linter that the README says finds possible errors such as invalid instance types for major cloud providers, warns about deprecated syntax and unused declarations, and enforces best practices and naming conventions. The provider-specific checks come from plugins you install, not from the core binary.

### How do I install TFLint on Windows?

The README gives a WinGet command for Windows: winget install -e --id TerraformLinters.tflint. The release archive route used on Linux is also available from the latest release page.

### How do I install TFLint on macOS?

The README lists Homebrew for macOS with the command brew install terraform-linters/tap/tflint. Installing from source with go install github.com/terraform-linters/tflint@latest is the other documented option.

### How do I install TFLint on Ubuntu or another Linux distribution?

The README downloads the release archive for linux_amd64, verifies checksums.txt with gh attestation verify and sha256sum, unzips it, and installs the binary to /usr/local/bin with sudo install -c -v tflint /usr/local/bin/. A Docker image at ghcr.io/terraform-linters/tflint is the alternative if you do not want a host install.

### What is TFLint in Terraform?

TFLint is a pluggable linter for Terraform configurations. The README describes it as a framework where each feature is provided by a plugin, with the Terraform-language ruleset bundled and cloud provider rulesets installed separately.

## Sources

- [Issues](https://github.com/terraform-linters/tflint/issues)
- [License: MPL-2.0](https://github.com/terraform-linters/tflint/blob/master/LICENSE)
- [README](https://github.com/terraform-linters/tflint/blob/master/README.md)
- [Releases](https://github.com/terraform-linters/tflint/releases)
- [terraform-linters/tflint on GitHub](https://github.com/terraform-linters/tflint)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/terraform-linters-tflint
