tflint Documentation

A Pluggable Terraform Linter

5,840 stars Go Go 21 files ยท ~13,730 tokens #terraform#tflint
RAW Doc

Docker & Self-Hosted Deployment Verified OCI

Deploy tflint on any server, homelab, or cloud container platform.

1-Click Terminal Command
bash (docker run)
docker run -d --name tflint --restart always terraform-linters/tflint:latest

Technical Documentation: terraform-linters/tflint

โ„น๏ธ Provenance: Multi-Tier Ingestion ยท CodeWiki Reference

Overview (`README.md`)

TFLint




A Pluggable Terraform Linter

Features

TFLint is a framework and each feature is provided by plugins, the key features are as follows:

  • Find possible errors (like invalid instance types) for Major Cloud providers (AWS/Azure/GCP).
  • Warn about deprecated syntax, unused declarations.
  • Enforce best practices, naming conventions.

Installation

Download the appropriate archive from the latest release, verify it, and install the binary:

console
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/

Homebrew (macOS):

console
brew install terraform-linters/tap/tflint

WinGet (Windows):

console
winget install -e --id TerraformLinters.tflint

Go:

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

Verification

Artifact Attestations are available that can be verified using the GitHub CLI.

console
gh attestation verify checksums.txt -R terraform-linters/tflint
sha256sum --ignore-missing -c checksums.txt

Cosign (Deprecated)

[!WARNING]
Cosign signatures are now deprecated. Please use GitHub CLI.

Cosign verify-blob command ensures that the release was built with GitHub Actions in this repository.

console
cosign verify-blob --certificate=checksums.txt.pem --signature=checksums.txt.keyless.sig --certificate-identity-regexp="^https://github.com/terraform-linters/tflint" --certificate-oidc-issuer=https://token.actions.githubusercontent.com checksums.txt
sha256sum --ignore-missing -c checksums.txt

Docker

Instead of installing directly, you can use the Docker image:

console
docker run --rm -v $(pwd):/data -t ghcr.io/terraform-linters/tflint

To download plugins, you can override the entrypoint to a shell (sh) to run --init and the main command in a single docker run command:

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

GitHub Actions

If you want to run on GitHub Actions, setup-tflint action is available.

Getting Started

First, enable rules for Terraform Language (e.g. warn about deprecated syntax, unused declarations). TFLint Ruleset for Terraform Language is bundled with TFLint, so you can use it without installing it separately.

The bundled plugin enables the "recommended" preset by default, but you can disable the plugin or use a different preset. Declare the plugin block in .tflint.hcl like this:

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

See the tflint-ruleset-terraform documentation for more information.

Next, If you are using an AWS/Azure/GCP provider, it is a good idea to install the plugin and try it according to each usage:

If you want to extend TFLint with other plugins, you can declare the plugins in the config file and easily install them with tflint --init.

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

See also Configuring Plugins.

You can discover plugins from other organizations on GitHub via the tflint-ruleset topic.

If you want to add custom rules that are not in existing plugins, you can build your own plugin or write your own policy in Rego. See Writing Plugins or OPA Ruleset.

Usage

TFLint inspects files under the current directory by default. You can change the behavior with the following options/arguments:

text
/* Detailed source-code truncated for AI context efficiency. */

See User Guide for details.

Debugging

If you don't get the expected behavior, you can see the detailed logs when running with TFLINT_LOG environment variable.

console
$ TFLINT_LOG=debug tflint

Developing

See Developer Guide.

Security

If you find a security vulnerability, please refer our security policy.

License

Please note that although much of this project is licensed under MPL 2.0, some files in the terraform package are licensed under BUSL 1.1.

For the reasons stated above, the executable forms (release binaries) is bound by both licenses.

See also https://discuss.hashicorp.com/t/hashicorp-projects-changing-license-to-business-source-license-v1-1/57106/7


Overview (`docs/README.md`)

TFLint Documentation


Overview (`terraform/README.md`)

Forked Terraform packages

This directory contains a subset of code from Terraform's internal packages. However, the implementation is not exactly the same, it is just a fork, and simplifications and changes have been made according to our project.

Previously, TFLint uses Terraform as a library, but due to the package internalization, it is no longer possible to import from external packages such as TFLint. This is why TFLint has its own fork.

This package provides functionality for static analysis of Terraform Language.


Overview (`docs/developer-guide/README.md`)

TFLint Developer Guide

The goal of this guide is to quickly understand how TFLint works and to help more developers contribute.

Table of Contents


Overview (`docs/user-guide/README.md`)

TFLint User Guide

This guide describes the various features of TFLint for end users.

Table of Contents


Overview (`terraform/ipaddr/README.md`)

Forked IP address parsing functions

This directory contains a subset of code from the Go project's net package
as of Go 1.16, used under the Go project license which we've included here
in LICENSE and PATENTS, which are also copied from
the Go project.

Terraform has its own fork of these functions because Go 1.17 included a
breaking change to reject IPv4 address octets written with leading zeros.

The Go project rationale for that change was that Go historically interpreted
leading-zero octets inconsistently with many other implementations, trimming
off the zeros and still treating the rest as decimal rather than treating the
octet as octal.

The Go team made the reasonable observation that having a function that
interprets a non-normalized form in a manner inconsistent with other
implementations may cause naive validation or policy checks to produce
incorrect results, and thus it's a potential security concern. For more
information, see Go issue #30999.

After careful consideration, the Terraform team has concluded that Terraform's
use of these functions as part of the implementation of the cidrhost,
cidrsubnet, cidrsubnets, and cidrnetmask functions has a more limited
impact than the general availability of these functions in the Go standard
library, and so we can't justify a similar exception to our Terraform 1.0
compatibility promises as the Go team made to their Go 1.0 compatibility
promises.

If you're considering using this package for new functionality other than the
built-in functions mentioned above, please do so only if consistency with the
behavior of those functions is important. Otherwise, new features are not
burdened by the same compatibility constraints and so should typically prefer
to use the stricter interpretation of the upstream parsing functions.


Overview (`terraform/tfhcl/README.md`)

Forked `hcl/ext/dynblock` package

This package is a fork of github.com/hashicorp/hcl/ext/dynblock. TFLint maintains its own dynblock implementation to support not only dynamic blocks, but also count/for_each meta-arguments in the HCL layer.


Developer Guide Api Compatibility (`docs/developer-guide/api_compatibility.md`)

API Compatibility

This is an internal documentation summarizing the currently supported SDK and TFLint versions and any compatibility caveats.

Protocol version: 11
SDK version: v0.16.0+
TFLint version: v0.46.0+


Developer Guide Architecture (`docs/developer-guide/architecture.md`)

Architecture

TFLint is a pluggable linter and does not contain any rule implementations. Rules are provided as plugins, these are launched by TFLint as subprocesses and communicate over gRPC.

An important part of understanding TFLint's architecture is that TFLint (host) and a plugin act as both gRPC server/client.

For example, the plugin (client) to the host (server) requests to:

  • Retrieve Terraform configs (e.g. aws_instance.main)
  • Evaluate expressions (e.g. var.foo)
  • Save reported issues to the host server

The host (client) to the plugin (server) requests to:

  • Apply plugin configs
  • Request to run inspections

The plugin system is implemented by TFLint plugin SDK. If you want to know more about *.proto and detailed gRPC server/client implementation, check out the SDK.

Inspection Flow Diagram

The following diagram explains how an inspection is performed when a user executes command:

text
/* Detailed source-code truncated for AI context efficiency. */

CLI (`cmd` package)

The cmd package is the entrypoint of the CLI. cmd.CLI has streams to stdout/stderr and prints a result to the screen.

Depending on the user's instructions, it does the following:

  • Run inspection
  • Initialize TFLint (install plugins)
  • Start language server
  • Print version info
  • Run bundled plugin (internal use)

This package is responsible for parsing CLI flags and arguments. The parsed cmd.Option is converted to tflint.Config and merged with a config file.

Load TFLint config (`tflint.LoadConfig`)

The tflint package provides many features related to TFLint, such as loading a config file (.tflint.hcl) and parsing annotations (# tflint-ignore comments).

The tflint.LoadConfig loads a config file and returns tflint.Config. This config will be used in later steps.

Load Terraform config (`terraform.LoadConfig`)

The terraform package is a fork of github.com/hashicorp/terraform/internal. This package is responsible for processing the Terraform semantics, such as parsing *.tf files, evaluating expressions, and loading modules.

The terraform.LoadConfig reads *.tf files as a terraform.Config in the given directory. These structures are designed to be as similar to Terraform core. See "The Design of terraform Package" section below for details.

Discover plugins (`plugin.Discovery`)

The plugin package is responsible for the plugin system. This package contains gRPC server implementation, installation, discovery, etc.

The plugin.Discovery discovers installed plugins and launches plugin binaries as subprocesses. This detailed implementation is hidden by github.com/hashicorp/go-plugin.

Launch plugin processes (go-plugin)

The go-plugin launches a plugin binary as a subprocess. The launched plugin acts as a gRPC server and communicates with the host process.

The plugin server is called "RuleSet" server. Its behavior is implemented by a plugin developer.

Apply configs to plugins (`ruleset.ApplyConfig`)

The plugin.Discovery returns a client for the RuleSet server it started. Use the ApplyConfig method to send the plugin config described in the config file to the server.

Request inspection to plugins (`ruleset.Check`)

Similarly, send inspection requests to the server. The server responds to requests and runs an inspection, but the plugin needs access to terraform.Config (imagine runner.GetResourceContent).

For this, the host process launches a gRPC server to respond such requests. The host server is called "Runner" server. Its behavior is implemented by TFLint. The plugin is passed a client that corresponds to the Runner server.

Respond to requests for Terraform config (`plugin.GRPCServer`)

The Runner server responds to requests from plugins to retrieve Terraform configs, evaluate expressions, etc. This implementation is contained in the plugin package.

Save issues emitted by plugins (`plugin.GRPCServer`)

The Runner server saves issues emitted by plugins (imagine runner.EmitIssue). The saved issues will be printed to the screen in the next step.

The formatter package processes and outputs issues in formats such as default, JSON, and SARIF.

Inspection Sequence Diagram

The following diagram explains how the host process, RuleSet server, and Runner server each behave in inspection:

mermaid
sequenceDiagram
  participant TFLint as TFLint (host)
  participant RuleSet as RuleSet (plugin)
  participant Runner as Runner (host as goroutie)
  TFLint->>RuleSet: Start server
  TFLint->>RuleSet: Apply plugin configs
  TFLint->>Runner: Start server
  TFLint->>RuleSet: Request to run inspection
  RuleSet->>Runner: Request to get Terraform configs
  Runner-->>RuleSet: Return resources/modules
  RuleSet->>Runner: Request to evaluate expressions
  Runner-->>RuleSet: Return evaluated values
  RuleSet->>Runner: Emit issues
  RuleSet-->>TFLint: End of inspection
  Runner-->>TFLint: Return issues

The Design of `terraform` Package

The terraform package is a fork of Terraform internal packages. It is based on the same Terraform core, but with some implementation changes specifically for static analysis.

The following diagram explains the dependencies of each component:

mermaid
flowchart TB
  files["Terraform config files<br/>(*.tf)"]-->parser["Language parser<br/>(terraform.Parser)"]
  subgraph child module
    modFiles["Terraform config files<br/>(*.tf)"]-->modParser["Language parser<br/>(terraform.Parser)"]
  end
  modParser-->modWalker["Module walker<br/>(terraform.ModuleWalker)"]  
  parser-- terraform.Module -->loader["Config loader<br/>(terraform.LoadConfig)"]
  modWalker-- terraform.Module -->loader
  loader-- terraform.Config -->gatherVars["Gather variables<br/>(terraform.VariableValues)"]
  valuesFile["Variable definitions files<br/>(*.tfvars)"]-- terraform.InputValues -->gatherVars
  cliFlag["CLI flags<br/>(--var)"]-- terraform.InputValues -->gatherVars
  gatherVars-- cty.Value -->evaluator["Evaluator<br/>(terraform.Evaluator)"]
  loader-- terraform.Config -->evaluator

HCL and cty

The underlying technologies of Terraform language are HCL and cty. The Terraform language implements its own semantics on top of these.

TFLint also leverages these technologies, making it highly compatible with Terraform.

Differences from Terraform

The basic architecture is the same as Terraform. The biggest difference is that the terraform.Evaluator is state (terraform.State) independent. See also Terraform Core Architecture Summary .

State is not always available for static analysis as it can only be obtained by running terraform plan/apply. TFLint solves this problem in the same way as Terraform. Terraform has the ability to handle unknown values during first-time planning, and TFLint take advantage of this to always treat dynamic values as unknowns.

It has the same basic design as Terraform, so it has the advantage of being able to easily support future language extensions of Terraform.

Lazy Schema Evaluation

Terraform has a predefined schema and uses it to decode HCL body. This is necessary to strictly define the syntax, but TFLint does not necessarily require a strict schema in order to support many versions of the Terraform language.

Against this background, terraform.Module in TFLint decodes minimal structure (e.g. terraform.Variable, terraform.Resource). The terraform.Module has native hcl.File and returns bodies based on the schema requested during inspection. In other words, it does lazy schema evaluation to achieve syntactic robustness.

For this reason, TFLint take a different approach to implementing for_each and count meta-arguments, and dynamic blocks than Terraform. Instead of being pre-expanded, it is expanded as an extension of hcl.Body when extracted by the schema.


Developer Guide Building (`docs/developer-guide/building.md`)

Building TFLint

Go 1.24 or higher is required to build TFLint from source code. Clone the source code and run the make command. Built binary will be placed in dist directory.

console
$ git clone https://github.com/terraform-linters/tflint.git
$ cd tflint
$ make
mkdir -p dist
go build -v -o dist/tflint

Run tests

If you change code, make sure that the tests you add and existing tests will be passed:

console
$ make test

Some tests depending on Git submodules. Running make test will update these automatically, but if you run tests directly with go test, you need to update submodules manually:

sh
git submodule init
git submodule update

Run E2E tests

You can check the actual CLI behavior by running the E2E tests. Since the E2E tests uses the installed tflint command, it is necessary to add the path into $PATH environment so that the binary built by go install can be referenced.

console
$ make e2e

Developer Guide Plugins (`docs/developer-guide/plugins.md`)

Writing Plugins

If you want to add custom rules, you can write ruleset plugins.

Overview

Plugins are independent binaries and use go-plugin to communicate with TFLint over gRPC. TFLint executes the binary when the plugin is enabled, and the plugin process must act as an gRPC server for TFLint.

If you want to create a new plugin, The template repository is available to satisfy these specification. You can create your own repository from "Use this template" and easily add rules based on some reference rules.

The template repository uses the SDK that wraps the go-plugin for communication with TFLint. See also the Architecture section for the architecture of the plugin system.

1. Creating a repository from the template

Visit tflint-ruleset-template and click the "Use this template" button. Repository name must be tflint-ruleset-*.

For public repositories, you can optionally add the tflint-ruleset topic to make your ruleset discoverable on the topic page.

2. Building and installing the plugin

The created repository can be installed locally with make install. Enable the plugin as follows and verify that the installed plugin works.

hcl
plugin "template" {
    enabled = true
}
console
$ make install
go build
mkdir -p ~/.tflint.d/plugins
mv ./tflint-ruleset-template ~/.tflint.d/plugins
$ tflint -v
TFLint version 0.28.1
+ ruleset.template (0.1.0)

3. Changing/Adding the rules

Rename the ruleset and add/edit rules. After making changes, you can check the behavior with make install. See also the tflint-plugin-sdk API reference for communication with the host process.

4. Creating a GitHub Release

You can build and install your own ruleset locally as described above, but you can also install it automatically with tflint --init.

The requirements to support automatic installation are as follows:

  • The built plugin binaries must be published on GitHub Release
  • The release must be tagged with a name like v1.1.1
  • The release must contain an asset with a name like tflint-ruleset-{name}_{GOOS}_{GOARCH}.zip
  • The zip file must contain a binary named tflint-ruleset-{name} (tflint-ruleset-{name}.exe in Windows)
  • The release must contain a checksum file for the zip file with the name checksums.txt
  • The checksum file must contain a sha256 hash and filename

When signing a release, one of the following requirements must be met:

  • Artifact Attestation
  • PGP signing key
    • The release must contain a signature file for the checksum file with the name checksums.txt.sig
    • The signature file must be binary OpenPGP format

Releases that meet these requirements can be easily created by following the GoReleaser config in the template repository.


Developer Guide Releasing (`docs/developer-guide/releasing.md`)

Releasing a new version of TFLint

Maintainers with push access to TFLint can publish new releases.
Keep the following in mind when publishing a release:

  • Avoid changing behavior in patch versions as much as possible. Patch version updates should only contain bug fixes.
    • However, if the behavior changes due to bug fixes, this is not the case.
  • Write readable release notes. It would be nice to know what the benefits are of updating to this version, or what needs to be changed to update.

To do this efficiently, releasing is automated as much as possible. After merging the changes you want to release into the master branch, run make release on the master branch.

console
$ make release

The make release does the following:

  • Accept new version as input and automatically rewrites the necessary files.
  • Automatically generate a release note. You can edit it interactively.
  • Run the required tests.
  • Create a commit, tag it and push it to the remote.

Note that this script does not build any binaries. These run on GitHub Actions. This script just pulls the trigger for it.


User Guide Annotations (`docs/user-guide/annotations.md`)

Annotations

TFLint supports several comment annotations for suppressing issues for specific lines or files. Annotations can only suppress issues emitted from fully valid, parseable Terraform modules. Errors cannot be ignored.

Rule configs can opt out of annotation-based suppression by setting
ignorable = false. In that case, tflint-ignore and tflint-ignore-file
annotations do not suppress issues from that rule.

Annotation comments can disable rules on specific lines:

hcl
resource "aws_instance" "foo" {
    # tflint-ignore: aws_instance_invalid_type
    instance_type = "t1.2xlarge"
}

Multiple rules can be specified as a comma-separated list:

hcl
resource "aws_instance" "foo" {
    # tflint-ignore: aws_instance_invalid_type, other_rule
    instance_type = "t1.2xlarge"
}

All rules can be ignored by specifying the all keyword:

hcl
resource "aws_instance" "foo" {
    # tflint-ignore: all
    instance_type = "t1.2xlarge"
}

It's a good idea to add a reason for why a rule is ignored, especially temporarily:

hcl
resource "aws_instance" "foo" {
    # This instance type is new and TFLint doesn't know about it yet
    # tflint-ignore: aws_instance_invalid_type
    instance_type = "t10.2xlarge"
}

Or, on the same line:

hcl
resource "aws_instance" "foo" {
  # tflint-ignore: aws_instance_invalid_type # too new for TFLint
  instance_type = "t10.2xlarge" 
}

The // comment style is also supported, but Terraform recommends #.

hcl
resource "aws_instance" "foo" {
  // tflint-ignore: aws_instance_invalid_type // too new for TFLint
  instance_type = "t10.2xlarge" 
}

Files

To disable an entire file, you can also use the tflint-ignore-file annotation:

hcl
# tflint-ignore-file: aws_instance_invalid_type

resource "aws_instance" "foo" {
  instance_type = "t1.2xlarge"
}

This annotation is valid only at the top of the file. The following cannot be used and will result in an error:

hcl
resource "aws_instance" "foo" {
  # tflint-ignore-file: aws_instance_invalid_type
  instance_type = "t1.2xlarge"
}
hcl
resource "aws_instance" "foo" { # tflint-ignore-file: aws_instance_invalid_type
  instance_type = "t1.2xlarge"
}

JSON

The tflint-ignore-file annotation is also supported in Terraform JSON by using a top-level comment property:

json
{
  "//": "tflint-ignore-file: aws_instance_invalid_type",
  "resource": {
    "aws_instance": {
      "foo": {
        "instance_type": "t2.micro"
      }
    }
  }
}

As with annotations in HCL files, multiple rules can be specified as a
comma-separated list:

json
{
  "//": "tflint-ignore-file: aws_instance_invalid_type, other_rule",
  "resource": {
    "aws_instance": {
      "foo": {
        "instance_type": "t2.micro"
      }
    }
  }
}

Similarly, annotations in JSON can be followed with arbitrary comments, but the annotation must be the first thing in the comment property string:

json
{
  "//": "tflint-ignore-file: aws_instance_invalid_type # This instance type is new and TFLint doesn't know about it yet",
  "resource": {
    "aws_instance": {
      "foo": {
        "instance_type": "t2.micro"
      }
    }
  }
}

The tflint-ignore annotation is not supported in JSON configuration.


User Guide Autofix (`docs/user-guide/autofix.md`)

Autofix

Some issues reported by TFLint can be auto-fixable. Auto-fixable issues are marked as "Fixable" as follows:

console
$ tflint
1 issue(s) found:

Warning: [Fixable] Single line comments should begin with # (terraform_comment_syntax)

  on main.tf line 1:
   1: // locals values
   2: locals {

When run with the --fix option, TFLint will fix issues automatically.

console
$ tflint --fix
1 issue(s) found:

Warning: [Fixed] Single line comments should begin with # (terraform_comment_syntax)

  on main.tf line 1:
   1: // locals values
   2: locals {

Please note that not all issues are fixable. The rule must support autofix.

If autofix is applied, it will automatically format the entire file. As a result, unrelated ranges may change.


User Guide Calling Modules (`docs/user-guide/calling-modules.md`)

Calling Modules

You can inspect not only the root module but also module calls. TFLint evaluates each call (i.e. module block) and emits any issues that result from the specified input variables.

hcl
module "aws_instance" {
  source        = "./module"

  ami           = "ami-b73b63a0"
  instance_type = "t1.2xlarge"
}
console
$ tflint
1 issue(s) found:

Error: instance_type is not a valid value (aws_instance_invalid_type)

  on template.tf line 5:
   5:   instance_type = "t1.2xlarge"

Callers:
   template.tf:5,19-31
   module/instance.tf:5,19-36

By default, TFLint only calls local modules whose the source is a relative path like ./*. If you want to call remote modules (registry, git, etc.), you must run terraform init (or terraform get) before invoking TFLint so that modules are loaded into the .terraform directory. After that, invoke TFLint with --call-module-type=all.

console
$ terraform init
$ tflint --call-module-type=all

The --call-module-type can also be set via configuration:

hcl
config {
  call_module_type = "all"
}

If you don't want to call any modules, pass --call-module-type=none:

console
$ tflint --call-module-type=none

If you want to ignore inspection for a particular module, you can use --ignore-module:

console
$ tflint --ignore-module=./module

Caveats

  • Issues must be associated with a variable that was passed to the module. If an issue within a child module is detected in an expression that does not reference a variable (var), it will be discarded.
  • Rules that evaluate syntax rather than content should ignore child modules.
  • If you want to evaluate all TFLint rules on non-root modules, inspect directly against the module directories. Note that there is a difference between calling a child module in an inspection and inspecting a child module as the root module.

User Guide Compatibility (`docs/user-guide/compatibility.md`)

Compatibility with Terraform

TFLint interprets the Terraform language with its own parser which is a fork of the Terraform's native one. This allows it to be parsed correctly even if Terraform is not installed at runtime.

The parser supports Terraform v1.x syntax and semantics. The language compatibility on Terraform v1.x is defined by Compatibility Promises. TFLint follows this promise. New features are only supported in newer TFLint versions, and bug and experimental features compatibility are not guaranteed.

The latest supported version is Terraform v1.16.

Input Variables

Like Terraform, TFLint supports the --var, --var-file options, environment variables (TF_VAR_*), and automatically loading variable definitions (terraform.tfvars and *.auto.tfvars) files. See Input Variables.

Input variables are evaluated just like in Terraform:

hcl
variable "instance_type" {
  default = "t2.micro"
}

resource "aws_instance" "foo" {
  instance_type = var.instance_type # => "t2.micro"
}

Unknown variables (e.g. no default) are ignored:

hcl
variable "instance_type" {}

resource "aws_instance" "foo" {
  instance_type = var.instance_type # => ignored
}

Sensitive or ephemeral variables are ignored. This is to avoid unintended disclosure.

hcl
variable "instance_type" {
  sensitive = true
  default   = "t2.micro"
}

resource "aws_instance" "foo" {
  instance_type = var.instance_type # => ignored
}

Local Values

TFLint supports Local Values.

hcl
variable "foo" {
  default = "variable value"
}

locals {
  static   = "static value"
  variable = var.foo
  local    = local.static
  resource = aws_instance.main.arn
}

local.static   # => "static value"
local.variable # => "variable value"
local.local    # => "static value"
local.resource # => ignored (unknown)

The `count` and `for_each` Meta-Arguments

TFLint supports the count and for_each meta-arguments.

hcl
resource "aws_instance" "foo" {
  count = 0

  instance_type = "invalid" # => ignored because the resource is not created
}
hcl
resource "aws_instance" "foo" {
  count = 2

  instance_type = "t${count.index}.micro" # => "t0.micro" and "t1.micro"
}

Note that this behavior may differ depending on a rule. Rules like terraform_deprecated_syntax will check resources regardless of the meta-argument values.

If the meta-arguments are unknown, the resource/module is ignored:

hcl
variable "count" {}

resource "aws_instance" "foo" {
  count = var.count

  instance_type = "invalid" # => ignored
}

The `path.*` and `terraform.*` Values

TFLint supports filesystem and workspace info.

  • path.module
  • path.root
  • path.cwd
  • terraform.workspace.

The terraform.applying always resolves to false.

Unsupported Named Values

The values below are state-dependent or cannot be determined statically, so TFLint resolves them to unknown values.

  • <RESOURCE TYPE>.<NAME>
  • resource.<RESOURCE TYPE>.<NAME>
  • ephemeral.<RESOURCE TYPE>.<NAME>
  • module.<MODULE NAME>
  • data.<DATA TYPE>.<NAME>
  • self.<NAME>

The ephemeral.<RESOURCE TYPE>.<NAME> always resolves to an unknown value marked as ephemeral, which is the same in most cases as anything else, but some rules may treat it differently.

Functions

Built-in Functions are fully supported. However, functions such as plantimestamp whose return value cannot be determined statically will return an unknown value.

Provider-defined functions always return unknown values, except for provider::terraform::* functions.

Dynamic Blocks

TFLint supports dynamic blocks.

hcl
resource "aws_instance" "dynamic" {
  dynamic "ebs_block_device" {
    for_each = toset([
      { size = 10 },
      { size = 20 }
    ])
    content {
      volume_size = ebs_block_device.value["size"] # => 10 and 20
    }
  }
}

Similar to support for meta-arguments, some rules may process a dynamic block as-is without expansion.

If the for_each is unknown, the expanded block will be empty. If the for_each is sensitive or ephemeral, TFLint expands dynamic blocks like Terraform does, but the mark does not propagate to children and expressions containing iterators resolve to unknown. This is a backwards compatibility limitation that may be resolved in a future version.

Modules

TFLint doesn't automatically inspect the content of modules themselves. However, by default, it will analyze their content in order to raise any issues that arise from attributes in module calls.

hcl
resource "aws_instance" "static" {
  ebs_block_device {
    encrypted = false # => Must be encrypted
  }
}

module "aws_instance" {
  source = "./module/aws_instance"

  encrypted = false # => Must be encrypted
}

Remote modules can also be inspected. See Calling Modules for details.

If the module source contains an unknown value, the module will be ignored:

hcl
variable "module_path" {
  const = true
}

module "aws_instance" {
  source = var.module_path

  encrypted = false # => ignored
}

Environment Variables

The following environment variables are supported:


User Guide Config (`docs/user-guide/config.md`)

Configuring TFLint

You can change the behavior not only in CLI flags but also in config files. TFLint loads config files according to the following priority order:

  1. File passed by the --config option
  2. File set by the TFLINT_CONFIG_FILE environment variable
  3. Current directory ./.tflint.hcl
  4. Current directory ./.tflint.json
  5. Home directory ~/.tflint.hcl
  6. Home directory ~/.tflint.json

The config file can be written in either HCL or JSON format, determined by the file extension. JSON files use the HCL-compatible JSON syntax, following the same structure as Terraform's .tf.json files. An HCL example is shown below:

hcl
tflint {
  required_version = ">= 0.50"
}

config {
  format = "compact"
  plugin_dir = "~/.tflint.d/plugins"

  call_module_type = "local"
  force = false
  disabled_by_default = false

  ignore_module = {
    "terraform-aws-modules/vpc/aws"            = true
    "terraform-aws-modules/security-group/aws" = true
  }

  varfile = ["example1.tfvars", "example2.tfvars"]
  variables = ["foo=bar", "bar=[\"baz\"]"]
}

plugin "aws" {
  enabled = true
  version = "0.4.0"
  source  = "github.com/terraform-linters/tflint-ruleset-aws"
}

rule "aws_instance_invalid_type" {
  enabled = false
}

The same configuration can be written in JSON format as .tflint.json:

json
{
  "tflint": {
    "required_version": ">= 0.50"
  },
  "config": {
    "format": "compact",
    "plugin_dir": "~/.tflint.d/plugins",
    "call_module_type": "local",
    "force": false,
    "disabled_by_default": false,
    "ignore_module": {
      "terraform-aws-modules/vpc/aws": true,
      "terraform-aws-modules/security-group/aws": true
    },
    "varfile": ["example1.tfvars", "example2.tfvars"],
    "variables": ["foo=bar", "bar=[\"baz\"]"]
  },
  "plugin": {
    "aws": {
      "enabled": true,
      "version": "0.4.0",
      "source": "github.com/terraform-linters/tflint-ruleset-aws"
    }
  },
  "rule": {
    "aws_instance_invalid_type": {
      "enabled": false
    }
  }
}

The file path is resolved relative to the module directory when --chdir or --recursive is used. To use a config file from the working directory when recursing, pass an absolute path:

sh
tflint --recursive --config "$(pwd)/.tflint.hcl"
# or
tflint --recursive --config "$(pwd)/.tflint.json"

`required_version`

Restrict the TFLint version used. This is almost the same as Terraform's required_version.
You can write version constraints in the same way.

`format`

CLI flag: --format

Change the output format. The following values are valid:

  • default
  • json
  • checkstyle
  • junit
  • compact
  • sarif

In recursive mode (--recursive), this field will be ignored in configuration files and must be set via a flag.

`plugin_dir`

Set the plugin directory. The default is ~/.tflint.d/plugins (or ./.tflint.d/plugins). See also Configuring Plugins

`call_module_type`

CLI flag: --call-module-type

Select types of module to call. The following values are valid:

  • all
  • local (default)
  • none

If you select all, you can call all (local and remote) modules. See Calling Modules.

hcl
config {
  call_module_type = "all"
}
console
$ tflint --call-module-type=all

`force`

CLI flag: --force

Return zero exit status even if issues found. TFLint returns the following exit statuses on exit by default:

  • 0: No issues found
  • 1: Errors occurred
  • 2: No errors occurred, but issues found

In recursive mode (--recursive), this field will be ignored in configuration files and must be set via a flag.

`disabled_by_default`

CLI flag: --only

Only enable rules specifically enabled in the config or on the command line. All other rules, including defaults, are disabled. Note, usage of --only on the command line will ignore other rules passed in via --enable-rule or --disable-rule.

hcl
config {
  disabled_by_default = true
  # other options here...
}

rule "aws_instance_invalid_type" {
  enabled = true
}

rule "aws_instance_previous_type" {
  enabled = true
}
console
$ tflint --only aws_instance_invalid_type --only aws_instance_previous_type

`ignore_module`

CLI flag: --ignore-module

Adding a module source to ignore_module will cause it to be ignored when calling modules. Note that you need to specify module sources rather than module ids for backward compatibility.

hcl
config {
  call_module_type = "all"
  ignore_module = {
    "terraform-aws-modules/vpc/aws"            = true
    "terraform-aws-modules/security-group/aws" = true
  }
}
console
$ tflint --ignore-module terraform-aws-modules/vpc/aws --ignore-module terraform-aws-modules/security-group/aws

`varfile`

CLI flag: --var-file

Set Terraform variables from tfvars files. If terraform.tfvars or any *.auto.tfvars files are present, they will be automatically loaded.

hcl
config {
  varfile = ["example1.tfvars", "example2.tfvars"]
}
console
$ tflint --var-file example1.tfvars --var-file example2.tfvars

`variables`

CLI flag: --var

Set a Terraform variable from a passed value. This flag can be set multiple times.

hcl
config {
  variables = ["foo=bar", "bar=[\"baz\"]"]
}
console
$ tflint --var "foo=bar" --var "bar=[\"baz\"]"

`rule` blocks

CLI flag: --enable-rule, --disable-rule

You can configure TFLint rules using rule blocks. Each rule's implementation specifies whether it will be enabled by default. In some rulesets, the majority of rules are disabled by default. Use rule blocks to enable them:

hcl
rule "terraform_unused_declarations" {
  enabled = true
}

The enabled attribute is required for all rule blocks. For rules that are enabled by default, set enabled = false to disable the rule:

hcl
rule "aws_instance_previous_type" {
  enabled = false
}

By default, annotations can suppress issues from a rule. Set ignorable = false
to prevent tflint-ignore and tflint-ignore-file annotations from suppressing
that rule:

hcl
rule "aws_instance_previous_type" {
  enabled   = true
  ignorable = false
}

Some rules support additional attributes that configure their behavior. See the documentation for each rule for details.

`plugin` blocks

You can declare the plugin to use. See Configuring Plugins

Rule config priority

The priority of rule configs is as follows:

  1. --only (CLI flag)
  2. --enable-rule, --disable-rule (CLI flag)
  3. rule blocks (config file)
  4. preset (config file, tflint-ruleset-terraform only)
  5. disabled_by_default (config file)

User Guide Editor Integration (`docs/user-guide/editor-integration.md`)

Editor Integration

TFLint can also act as a Language Server to integrate with various editors. This server conforms to Language Server Protocol v3.14.0 and can be used with an editor that implements any client. The following is an example in VS Code:

This server can be started with the --langserver option:

console
$ tflint --langserver
14:21:51 cli.go:185: Starting language server...

Currently, it only supports diagnostics and subscribes the following methods:

  • initialize
  • initialized
  • shutdown
  • exit
  • textDocument/didOpen
  • textDocument/didClose
  • textDocument/didChange
  • workspace/didChangeWatchedFiles

User Guide Environment Variables (`docs/user-guide/environment_variables.md`)

Environment Variables

Below is a list of environment variables available in TFLint.

  • TFLINT_LOG
  • TFLINT_CONFIG_FILE
  • TFLINT_PLUGIN_DIR
  • TFLINT_DISABLE_VERSION_CHECK
    • Disable version update notifications when running tflint --version. Set to 1 to disable.
  • GITHUB_TOKEN
    • (Optional) Used for authenticated GitHub API requests when checking for updates and downloading plugins. Increases the rate limit from 60 to 5000 requests per hour. Useful if you encounter rate limit errors. You can obtain a token by creating a GitHub personal access token; no special scopes are required.
  • TFLINT_EXPERIMENTAL
    • Enable experimental features. Note that experimental features are subject to change without notice. Currently there is no impact.
  • TF_VAR_name
  • TF_DATA_DIR
  • TF_WORKSPACE

User Guide Plugins (`docs/user-guide/plugins.md`)

Configuring Plugins

You can extend TFLint by installing any plugin. Declare plugins you want to use in the config file as follows:

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

After declaring the version and source, tflint --init can automatically install the plugin.

console
$ tflint --init
Installing "foo" plugin...
Installed "foo" (source: github.com/org/tflint-ruleset-foo, version: 0.1.0)
$ tflint -v
TFLint version 0.28.1
+ ruleset.foo (0.1.0)

See also Configuring TFLint for the config file schema.

Attributes

This section describes the attributes reserved by TFLint. Except for these, each plugin can extend the schema by defining any attributes/blocks. See the documentation for each plugin for details.

`enabled` (required)

Enable the plugin. If set to false, the rules will not be used even if the plugin is installed.

`source`

The source URL to install the plugin. Must be in the format github.com/org/repo.

`version`

Plugin version. Do not prefix with "v". This attribute cannot be omitted when the source is set. Version constraints (like >= 0.3) are not supported.

`signature`

Controls how TFLint verifies plugin releases. Valid values are:

  • auto: Prefer attestation when available, then fall back to pgp. This is the default behavior.
  • attestation: Require artifact attestations. Attestation verification in private repositories is not supported.
  • pgp: Require PGP signature verification with signing_key.
  • none: Skip plugin signature verification.

`signing_key`

The signing key used when signature = "pgp" or "auto".

Plugin directory

Plugins are usually installed under ~/.tflint.d/plugins. Exceptionally, if you already have ./.tflint.d/plugins in your working directory, it will be installed there.

The automatically installed plugins are placed as [plugin dir]/[source]/[version]/tflint-ruleset-[name]. (tflint-ruleset-[name].exe in Windows).

If you want to change the plugin directory, you can change this with the plugin_dir or TFLINT_PLUGIN_DIR environment variable.

Avoiding rate limiting

When you install plugins with tflint --init, TFLint calls the GitHub API to get release metadata. By default, this is an unauthenticated request, subject to a rate limit of 60 requests per hour per IP address.

Background: GitHub REST API: Rate Limiting

If you fetch plugins frequently in CI, you may hit this rate limit. If you run TFLint in a shared CI environment such as GitHub Actions, you will share this quota with other tenants and may encounter rate limiting errors regardless of how often you run TFLint.

To increase the rate limit, you can send an authenticated request by authenticating your requests with an access token, by setting the GITHUB_TOKEN environment variable. In GitHub Actions, you can pass the built-in GITHUB_TOKEN that is injected into each job.

It's also a good idea to cache the plugin directory, as TFLint will only send requests if plugins aren't installed. The setup-tflint action includes an example of caching in GitHub Actions.

If you host your plugins on GitHub Enterprise Server (GHES), you may need to use a different token than on GitHub.com. In this case, you can use a host-specific token like GITHUB_TOKEN_example_com. The hostname must be normalized with Punycode. Use "_" instead of "." and "__" instead of "-".

hcl
# GITHUB_TOKEN will be used
plugin "foo" {
  source = "github.com/org/tflint-ruleset-foo"
}

# GITHUB_TOKEN_example_com will be used preferentially and will fall back to GITHUB_TOKEN if not set.
plugin "bar" {
  source = "example.com/org/tflint-ruleset-bar"
}

Keeping plugins up to date

We recommend using automatic updates to keep your plugin version up-to-date. Renovate supports TFLint plugins to easily set up automated update workflows.

Manual installation

You can also install the plugin manually. This is mainly useful for plugin development and for plugins that are not published on GitHub. In that case, omit the source and version attributes.

hcl
plugin "foo" {
  enabled = true
}

When enabled, TFLint looks for the plugin binary following the pattern tflint-ruleset-[name] (or tflint-ruleset-[name].exe on Windows) directly in your plugins folder (e.g., ~/.tflint.d/plugins/ on Unix-like systems or C:\Users\[username]\.tflint.d\plugins\ on Windows).

Thus, with the configuration above where the plugin name is "foo", the executable must be named tflint-ruleset-foo (or tflint-ruleset-foo.exe on Windows). So you should move the binary into the plugin directory in advance.

Bundled plugin

TFLint Ruleset for Terraform Language is built directly into TFLint binary. This is called a bundled plugin. Unlike other plugins, bundled plugins can be used without installation.

A bundled plugin is enabled by default without a plugin block declaration. The default config is below:

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

You can also change the behavior of the bundled plugin by explicitly declaring a plugin block.

If you want to use a different version of tflint-ruleset-terraform instead of the bundled plugin, you can install it with tflint --init by specifying the version and source. In this case the bundled plugin will not be automatically enabled.

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

  version = "0.1.0"
  source  = "github.com/terraform-linters/tflint-ruleset-terraform"
}

If you have tflint-ruleset-terraform manually installed, the bundled plugin will not be automatically enabled. In this case the manually installed version takes precedence.


User Guide Working Directory (`docs/user-guide/working-directory.md`)

Switching working directory

TFLint has --chdir and --recursive flags to inspect modules that are different from the current directory.

The --chdir flag is available just like Terraform:

console
$ tflint --chdir=environments/production

Its behavior is the same as Terraform's behavior. You should be aware of the following points:

  • Config files are loaded after acting on the --chdir option.
    • This means that tflint --chdir=dir will loads dir/.tflint.hcl instead of ./.tflint.hcl.
  • Relative paths are always resolved against the changed directory.
    • If you want to refer to the file in the original working directory, it is recommended to pass the absolute path using realpath(1) etc. e.g. tflint --config=$(realpath .tflint.hcl).
  • The path.cwd represents the original working directory. This is the same behavior as using --chdir in Terraform.

The --recursive flag enables recursive inspection. This is the same as running with --chdir for each directory.

console
$ tflint --recursive

Recursive inspection is performed in parallel by default. The default parallelism is the number of CPUs. This can be controlled with --max-workers.

These flags are also valid for --init and --version. Recursive init is required when installing required plugins all at once:

console
$ tflint --recursive --init
$ tflint --recursive --version
$ tflint --recursive

--- METRICS ---