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/kb/CI/CD/tf-fmt-validate-ci

kb/cicd ── CI/CD ── intermediate

pre-commit, fmt -check, and validate in CI

The pre-commit framework (Python) runs hooks on `git commit` locally and the same set in CI. The standard Terraform set: `terraform fmt -check` (no auto-fix, fails on unformatted code), `terraform validate`, and `tflint`. The config file is `.pre-commit-config.yaml` in the repository root. The goal is to catch obvious mistakes before they reach a CI runner that costs money.

view as markdownaka: terraform-pre-commit, terraform-ci-validate, terraform-pre-commit-hooks

Why this matters

A bug costs least when caught early. The chain:

  1. In the editor. An IDE plugin (HashiCorp Terraform extension for VS Code, IntelliJ HCL) shows errors immediately.
  2. At pre-commit. The hook blocks unformatted or invalid code from entering the repository.
  3. In CI. The last checkpoint, catching whatever slipped past the local hook.
  4. In review. A human.

pre-commit sits at the second level. It also covers the third: CI runs the same commands that developers run locally.

Installation

bash
pip install pre-commit

In the repository, .pre-commit-config.yaml:

yaml
repos:
  - repo: https://github.com/antonbabenko/pre-commit-terraform
    rev: v1.95.0
    hooks:
      - id: terraform_fmt
      - id: terraform_validate
      - id: terraform_tflint
        args:
          - --args=--minimum-failure-severity=warning
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v5.0.0
    hooks:
      - id: end-of-file-fixer
      - id: trailing-whitespace
      - id: check-merge-conflict

Register the hooks with git:

bash
pre-commit install

From this point on, git commit runs the hooks. If any hook fails, the commit is rejected.

What the main hooks do

HookWhat it runsEffect on files
terraform_fmtterraform fmt (with auto-fix)May modify files. Re-stage and commit again.
terraform_validateterraform validateNo file changes. Fails on syntax errors and init problems.
terraform_tflinttflintNo file changes. Fails on tflint rule violations.
terraform_docsRegenerates README with inputs/outputsModifies files.
terraform_trivytrivy configNo file changes. Security findings.
terraform_checkovcheckovNo file changes. Security findings.

You do not need all of them. The baseline is terraform_fmt plus terraform_validate. tflint is useful. Trivy and Checkov are slow per commit; most teams add them only in CI.

fmt: auto-fix vs check-only

For local pre-commit, let terraform_fmt apply auto-fix: changes are applied automatically, the developer runs git add and commits again. No manual editing needed.

In CI, use terraform fmt -check without auto-fix. If the code is not formatted, the step fails. This catches cases where the developer skipped pre-commit (committed through the UI or edited in a merge request directly).

Configuration in .pre-commit-config.yaml:

yaml
- id: terraform_fmt
  args:
    - --args=-recursive
    # no -check; auto-fix is desirable locally

In CI, run:

bash
terraform fmt -check -recursive .

validate: what it checks and what it does not

bash
terraform init -backend=false  # no real backend required
terraform validate

validate checks:

  • HCL syntax.
  • That the referenced resources exist in the providers.
  • That variable and attribute types match.
  • That there are no dependency cycles (though plan catches those too).

It does not check:

  • Whether the cloud will accept the config (that is what plan shows).
  • Whether variable values are valid (only validation blocks do that).
  • Whether dependencies actually work end-to-end (that requires apply).

validate is a quick sanity check. Without it, a typo in an attribute name can slip through and surface 30 minutes later when plan runs in CI.

See tf-validate.

tflint hook

yaml
- id: terraform_tflint
  args:
    - --args=--minimum-failure-severity=warning
    - --args=--enable-rule=terraform_naming_convention

tflint focuses on style and provider-specific best practices, not security. Examples: "aws_instance.instance_type is not a valid type", "module without a version constraint", "output without a description".

Teams that add tflint to pre-commit typically see code-review noise drop significantly.

Running all hooks at once

bash
pre-commit run --all-files

Use this when:

  • You just enabled pre-commit on a legacy repository.
  • You want to confirm the entire repository is clean before a merge.
  • CI runs this instead of separate steps.

CI pipeline

yaml
# .github/workflows/lint.yml
on: [pull_request]
jobs:
  pre-commit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: 1.9.8
      - run: |
          curl -L https://github.com/terraform-linters/tflint/releases/download/v0.55.1/tflint_linux_amd64.zip -o tflint.zip
          unzip tflint.zip
          sudo mv tflint /usr/local/bin/
      - uses: pre-commit/action@v3.0.1

pre-commit/action installs pre-commit itself and runs pre-commit run --all-files. One step covers all hooks.

Skip mechanism

Sometimes you need to commit while bypassing a hook (for example, fixing a blocking bug when the formatting issue is not the priority right now):

bash
SKIP=terraform_fmt git commit -m "fix: ..."

This is a local bypass only; CI will still check. Use sparingly.

To skip pre-commit entirely:

bash
git commit --no-verify

This is poor practice. Find out why the hook is failing instead.

Updating hook versions

bash
pre-commit autoupdate

This updates the rev: fields in .pre-commit-config.yaml. Review the git diff, open a PR, run pre-commit run --all-files, and confirm nothing broke.

Pitfalls

  • pre-commit only checks files changed in the commit. If you do not modify a .tf file, terraform_fmt will not check it. To scan the whole repository, run pre-commit run --all-files. CI typically runs exactly this command, catching files that were left unformatted in earlier commits.

  • The terraform_validate hook needs init. At the pre-commit stage, init may not have run. pre-commit-terraform performs init with -backend=false automatically, but this takes time and makes pre-commit slow on large projects. An alternative: run validate only in CI.

  • Hooks only see tracked files. Files listed in .gitignore are not checked. This is usually acceptable, but .tfvars files are often ignored, so validate will not cover them.

  • autoupdate can break things. A new major version of a hook often changes its arguments. Update deliberately, in a separate PR, and run --all-files to verify.

  • pre-commit-terraform requires the terraform CLI. If a developer does not have it installed locally, the hook fails immediately. Document prerequisites in the README, or use a devcontainer or asdf to provide a reproducible environment.

  • CI must duplicate pre-commit. Never rely solely on local pre-commit. A developer can bypass it (--no-verify, commit through the UI, or forget to run pre-commit install). CI is the only real gate.

§ команды

bash
pre-commit install

Register hooks in .git/hooks. Run once after cloning the repository.

bash
pre-commit run --all-files

Run all hooks against the entire repository. Use for legacy initialization or a pre-merge sweep.

bash
terraform fmt -check -recursive .

Canonical CI step: no auto-fix, exits with code 3 if any file differs.

bash
terraform init -backend=false && terraform validate

Minimal validate chain. -backend=false avoids connecting to a real S3 state backend.

bash
SKIP=terraform_fmt git commit -m '...'

Skip one hook for one commit. Use only when the skip is deliberate.

§ см. также

  • tf-fmtterraform fmt: canonical HCL formatting`terraform fmt` rewrites HCL to a canonical style: consistent indentation, aligned `=` signs, no extra blank lines. It runs on the current directory by default, or recursively with `-recursive`. In CI, use `-check -diff` to fail the build on unformatted files.
  • tf-validateterraform validate: checking HCL without the cloud`terraform validate` checks HCL for syntax errors and basic logic issues: unknown arguments, wrong types, and references to resources that do not exist. It does not contact the cloud and does not touch state, so it runs fast. In CI, run it after `init -backend=false` and before `plan`.
Footer
linuxlab-
Copyright © 2026 LinuxLab. All rights reserved.
Tutorials
Pricing
About
Privacy & cookies