linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
Intro
Lessons
Footer
linuxlab-TutorialsPricingAboutPrivacy & cookies
Copyright © 2026 LinuxLab. All rights reserved.
linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
  • Introduction
  • Lessons
  • How it works
  • Knowledge base
  • Cheat sheet
  • Capstone
  • Interview prep
home/terraform/lessons/tf-production-01-fmt-validate-tflint

lesson ── terraform-production ── ~14 мин ── 6 шагов

Linters: fmt, validate, tflint

The production track starts with hygiene. Three tools that no serious team works without: terraform fmt -check (formatting as a gate), terraform validate (syntax and gross errors), tflint (style and provider-specific lints). All three are fast, and all three belong in pre-commit and CI.

▶ интерактивный sandbox

Поднимется пара контейнеров: terraform 1.9 и localstack 3.8 в одной сети. В браузере откроется терминал, можно сразу terraform init. Каждый шаг проверяется автоматически. TTL 45 минут, без регистрации.

запустить sandbox →

stack ── terraform · localstack · 1 GB RAM · самоуничтожается через 45 мин простоя

Шаги

  1. 01

    Drop in deliberately unclean HCL

    bash
    cd /home/student/tf-lint
    cat > main.tf <<'EOF'
    resource "aws_s3_bucket"    "demo"{
    bucket    =     "linuxlab-lint-demo"
      tags={Owner="student"}
    }
    output "name"      {
      value=aws_s3_bucket.demo.bucket
    }
    EOF

    The file has extra spaces, hugged braces, bad indentation, and tags={...} with no spaces. A clean antipattern.

    ✓ Dirty HCL is in place. Now we show you how the linters see it.

  2. 02

    terraform fmt -check catches the mess

    bash
    terraform fmt -check
    echo "exit: $?"

    You should see a list of unclean files and exit 3:

    main.tf
    exit: 3

    -check does NOT fix anything, it only reports. In pre-commit you usually run without -check (auto-fix), in CI with -check (gate). See tf-fmt.

    Now run it without the flag, auto-fix:

    bash
    terraform fmt
    cat main.tf

    The file is aligned. No more ={}=, the indentation is stable.

    ✓ fmt normalized the HCL. That is exactly its job.

  3. 03

    Break a reference and let validate catch it

    Change the output so it points to an attribute that does not exist:

    bash
    sed -i 's/aws_s3_bucket\.demo\.bucket/aws_s3_bucket.demo.nonexistent/' main.tf
    cat main.tf

    Validate without the cloud:

    bash
    terraform init -backend=false -input=false
    terraform validate

    You should see something like:

    Error: Unsupported attribute
      on main.tf line N: ...
      This object has no argument, nested block, or exported attribute named "nonexistent".

    validate knows the provider schema and catches the typo. Without it the problem would surface only at plan, a minute later.

    Roll it back:

    bash
    sed -i 's/aws_s3_bucket\.demo\.nonexistent/aws_s3_bucket.demo.bucket/' main.tf
    terraform validate

    It should say "Success!".

    ✓ validate confirmed the config. On to tflint.

  4. 04

    tflint and its AWS plugin

    Without plugins tflint checks only generic rules. For AWS specifics you need tflint-ruleset-aws. The config, .tflint.hcl:

    bash
    cat > .tflint.hcl <<'EOF'
    plugin "aws" {
      enabled = true
      version = "0.32.0"
      source  = "github.com/terraform-linters/tflint-ruleset-aws"
    }
    rule "terraform_naming_convention" {
      enabled = true
    }
    rule "terraform_required_version" {
      enabled = true
    }
    EOF
    tflint --init

    --init pulls in the plugin (this is a network request). It should say "Installing aws plugin".

    ✓ tflint installed the AWS plugin. Now run the check.

  5. 05

    tflint finds provider-specific problems

    Run it on the current code:

    bash
    tflint --no-color
    echo "exit: $?"

    Most likely no errors, the HCL is simple. Add a deliberately bad resource, an aws_instance with a type that does not exist:

    bash
    cat >> main.tf <<'EOF'
    resource "aws_instance" "bad" {
      ami           = "ami-foobar"
      instance_type = "t2.bogus"
    }
    EOF
    tflint --no-color
    echo "exit: $?"

    You should see something like aws_instance_invalid_type: "t2.bogus" is an invalid instance type. This is specific to the AWS plugin, which knows every type.

    Remove it:

    bash
    sed -i '/aws_instance "bad"/,/^}$/d' main.tf
    tflint --no-color && echo "tflint clean"

    ✓ tflint catches provider problems that validate does not see.

    The same thing on OpenTofu

    OpenTofu keeps the CLI and state compatible with Terraform for the commands in this step: migration usually goes through mv .terraform .terraform.bak; tofu init -upgrade. But on the first switch, back up the state and do a run on a feature branch, the differences concentrate in newer features (variables in the backend, state encryption, OCI registry-backed modules). See tf-opentofu-parity for the full matrix.

    • → OpenTofu parity
  6. 06

    All three in one script

    The canonical lint pipeline:

    bash
    cat > lint.sh <<'EOF'
    #!/usr/bin/env bash
    set -euo pipefail
    echo "==> fmt"
    terraform fmt -check -recursive
    echo "==> validate"
    terraform init -backend=false -input=false -no-color > /dev/null
    terraform validate -no-color
    echo "==> tflint"
    tflint --no-color
    echo "All lints passed."
    EOF
    chmod +x lint.sh
    ./lint.sh

    Any step that fails exits 1, and the rest do not run. This is the basic lint job in CI. See tf-fmt-validate-ci about wiring it into pre-commit and GitHub Actions.

    ✓ The pipeline gate is ready. Next, pre-commit.

    What tflint does beyond invalid_type

    Base rules without plugins:

    • terraform_deprecated_interpolation, the old ${...} syntax.
    • terraform_unused_declarations, a variable/output that nothing references.
    • terraform_naming_convention, the snake_case convention.
    • terraform_required_version / terraform_required_providers, their absence = warning.

    The AWS plugin:

    • aws_instance_invalid_type, aws_db_instance_invalid_type.
    • aws_iam_policy_invalid_principal_format.
    • aws_s3_bucket_name (length, characters, convention).
    • aws_resource_missing_tags, required tags (turned on through config).

    Config for required tags:

    hcl
    rule "aws_resource_missing_tags" {
      enabled = true
      tags    = ["CostCenter", "Environment"]
    }

    This is a business-policy gate at the linter level.

    • → terraform fmt
    • → terraform validate
    • → Linters in CI

Что ты узнал

fmt -check breaks on unclean formatting, validate on syntax and the basic schema, tflint on style and provider best practices. In CI they run in one job (lint), and exit 1 at any stage fails the whole PR.

команды

  • terraform fmt -check -recursivecheck that the HCL is formatted. Exit 3 = there is a difference.
  • terraform init -backend=false && terraform validatesyntax without the cloud. -backend=false avoids stopping when S3 is missing.
  • tflint --init && tflint --recursivelint every directory. --init pulls in the plugins.

концепции

  • · fmt is reformatting (auto-fix locally), but -check mode only reports
  • · validate runs after init, and can run without a backend
  • · tflint is configured through .tflint.hcl: specific rules and provider plugins

← предыдущий

Your first module: move S3 into something reusable

следующий →

CDKTF, Terraform from TypeScript

Footer
linuxlab-
Copyright © 2026 LinuxLab. All rights reserved.
Tutorials
Pricing
About
Privacy & cookies