skip to content

What does TFLint catch in Terraform code that `terraform validate` cannot, and how does it know?

level: middleimportance: should knowfreq 48%

answer

  1. opinions and values, not shape
  2. the schema never enumerates valid values
  3. plugins carry provider knowledge offline
  4. tflint --init fetches what .tflint.hcl declares
  5. a bundled table has a release date

basics

~20 s

TFLint adds opinionated and provider-specific rules on top of schema correctness: unused declarations, deprecated idioms, naming conventions, and values a provider will reject such as a nonexistent instance type. Its provider rulesets ship that knowledge as plugins, so the checks stay offline.

solid answer

~50 s

`terraform validate` can only check what the provider schema describes — argument names, requiredness, types. It has no idea which *values* are legal, and it has no opinions. TFLint fills both gaps. Its bundled Terraform ruleset flags things like `terraform_unused_declarations` for variables and locals nothing references, deprecated syntax, and naming conventions. Its provider rulesets — installed as plugins via a `plugin` block in `.tflint.hcl` and fetched with `tflint --init` — carry generated knowledge of that provider's valid values, so a rule such as `aws_instance_invalid_type` rejects a made-up instance type that validate happily accepts. Because that knowledge is bundled rather than queried, the lint stays fast and needs no credentials, and it runs in the same cheap early stage as fmt and validate. What it is not is a security scanner: open security groups and unencrypted storage belong to Checkov or Trivy.

code

hcl · 13 lines
hcl
plugin "aws" {
  enabled = true
  version = "0.31.0"
  source  = "github.com/terraform-linters/tflint-ruleset-aws"
}

rule "terraform_unused_declarations" {
  enabled = true
}

rule "terraform_naming_convention" {
  enabled = true
}

go deeper

for a junior

Know that TFLint is a linter you run alongside terraform validate, that it needs a .tflint.hcl and tflint --init for provider rules, and that it flags things like unused variables.

for a middle

Explain the boundary precisely: validate checks argument names and types against the schema, while TFLint adds value-level and convention rules whose knowledge is bundled in a plugin rather than queried live.

for a senior

Show that you tune rulesets rather than accept defaults, pin and upgrade plugin versions so a new instance family does not break the build, and keep lint distinct from your security control coverage.

for a principal

Own the question of which findings block a merge and which are advisory — a linter that fires noisily on deliberate team choices gets disabled wholesale, which is worse than a narrower ruleset everyone trusts.

## The gap TFLint fills After `terraform validate`, you know the configuration is internally consistent and matches the provider schema. You still know nothing about two large categories of defect: 1. **Values the provider or API will reject.** The schema says `instance_type` is a string. It does not enumerate which strings exist. `"t3.mikro"` is a perfectly valid string. 2. **Things that are legal but wrong.** A variable nobody references. A deprecated idiom. A resource named inconsistently with the rest of the repo. A missing `required_version`. None of these is a schema violation, so validate is silent. TFLint is the linter for exactly that band — above schema correctness, below security policy. ## Two sources of rules TFLint ships a **bundled Terraform ruleset** covering tool-level and style concerns. Well-known rules include: - `terraform_unused_declarations` — variables, locals or data sources that nothing consumes; - `terraform_deprecated_interpolation` — the legacy `"${var.x}"` wrapping where a bare expression now belongs; - `terraform_required_version` — the root module fails to pin a Terraform version constraint; - `terraform_naming_convention` — resource and variable names that do not match the configured convention. Separately, **provider rulesets** are installed as plugins. You declare them in `.tflint.hcl` and run `tflint --init` to download them: ```hcl plugin "aws" { enabled = true version = "0.31.0" source = "github.com/terraform-linters/tflint-ruleset-aws" } rule "terraform_naming_convention" { enabled = true } ``` The AWS ruleset is where value knowledge lives — rules like `aws_instance_invalid_type` are generated from the provider's known-valid values and shipped inside the plugin. That is the answer to the "how does it know" half of the question: it is not calling an API at lint time, it is consulting a table that was baked into the plugin when it was released. The consequence is good and bad. Good: the check is fast, deterministic, and needs no credentials, so it belongs in the same early CI stage as fmt and validate. Bad: the table has a release date. A newly launched instance family can be flagged as invalid until the ruleset is upgraded, so pin the plugin version and expect to bump it. ## Running it ```bash tflint --init # download the plugins named in .tflint.hcl tflint --recursive # lint the root module and everything beneath it tflint --format=json # machine-readable output for CI annotation ``` Like `terraform fmt`, TFLint examines a single directory unless you ask it to recurse — the same trap where a root-level lint passes while `modules/` goes unchecked. It also supports SARIF output, which is what you want if your platform ingests findings into a code-scanning view. Rules are individually enablable and disablable in `.tflint.hcl`, and inline comments can suppress a specific finding. Both matter in practice, because a linter that fires on things a team has consciously decided to do gets switched off entirely rather than tuned — and a disabled linter finds nothing. ## What TFLint is not It is not a security scanner. "This security group allows 0.0.0.0/0 on port 22" and "this bucket has no encryption configured" are compliance findings, and the tools for those are Checkov, Trivy and the like, whether run over HCL or over JSON plan output. Some overlap exists at the edges, but treating TFLint as your control coverage is a mistake reviewers notice. It is also not a test framework. It never evaluates whether your module produces the right resources for a given set of inputs — that is `terraform test` with `.tftest.hcl` files, or a Go integration test. ## How to frame it in an interview The crisp version: validate checks the shape, TFLint checks the content and the conventions, scanners check the risk, and the test framework checks the behaviour. Naming the boundary between the first two — schema versus bundled value knowledge — is what shows you have actually run the tool rather than read its README.

  • Your build broke overnight because TFLint now flags a valid, newly released instance type. What is going on and how do you handle it?
    The provider ruleset carries a generated list of valid values from the day it was built, so anything launched afterwards looks invalid. Bump the pinned plugin version in `.tflint.hcl` and re-run `tflint --init`. If that release is not out yet, disable that single rule temporarily rather than the whole ruleset, and put the version bump on the backlog.
  • Where would you place TFLint relative to terraform validate and a security scanner in a pipeline?
    Immediately after validate and before anything that needs credentials. Lint is offline and fast, so it belongs in the cheap early stage where a fork's pull request can run it safely. Security scanning is the layer after, and if it runs against JSON plan output it necessarily comes later still because it needs an initialized directory and a successful plan.

saying these in an interview costs you the question

  • Says TFLint calls the provider API to check values
  • Treats TFLint as a security or compliance scanner
  • Thinks terraform validate already flags unused variables
  • Expects a plugin to know instance types launched after its release
  • Runs tflint at the root and assumes modules were linted

context