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-02-pre-commit

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

pre-commit hooks for Terraform

pre-commit is a Python framework for git hooks. You declare a set of checks in .pre-commit-config.yaml, run pre-commit install, and now every git commit runs fmt/validate/tflint before letting your changes into the repo. The same config, in CI: pre-commit run --all-files.

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

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

запустить sandbox →

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

Шаги

  1. 01

    Put working HCL into a git repo

    bash
    cd /home/student/tf-precommit
    git init -q
    git config user.email "student@linuxlab.local"
    git config user.name "student"
    cat > main.tf <<'EOF'
    resource "aws_s3_bucket" "demo" {
      bucket = "linuxlab-precommit-demo"
      tags = {
        Owner = "student"
      }
    }
    EOF
    terraform fmt
    git add .
    git commit -q -m "init: clean hcl"
    git log --oneline

    One clean commit in the repo, the baseline.

    ✓ Git is initialized, a clean baseline is committed.

  2. 02

    Create .pre-commit-config.yaml

    bash
    cat > .pre-commit-config.yaml <<'EOF'
    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=--init
      - repo: https://github.com/pre-commit/pre-commit-hooks
        rev: v5.0.0
        hooks:
          - id: end-of-file-fixer
          - id: trailing-whitespace
    EOF

    Notice: the hook ID is terraform_fmt, not just fmt. That is the name from the antonbabenko/pre-commit-terraform repo, the de facto standard for Terraform.

    Install the hooks into git:

    bash
    pre-commit install
    ls .git/hooks/pre-commit

    The file .git/hooks/pre-commit is now there. It is a wrapper that runs pre-commit on every commit.

    ✓ The hook is installed, now git commit goes through pre-commit.

  3. 03

    The hook catches a dirty commit

    Drop in a deliberately messy file:

    bash
    cat > badly.tf <<'EOF'
    resource "aws_s3_bucket"   "another"  {
    bucket="bad-format"
    }
    EOF
    git add badly.tf
    set +e
    git commit -m "feat: add another bucket"
    echo "exit: $?"
    set -e

    The commit failed because terraform_fmt found a problem. pre-commit applied the auto-fix itself, but the files stayed unstaged.

    Check:

    bash
    git status
    cat badly.tf

    badly.tf is now formatted, but git already sees it as "modified": pre-commit changed it after git add.

    ✓ The hook auto-fixed the format. Without it the commit would have gone through with dirty HCL.

  4. 04

    Re-stage and finish the commit

    bash
    git add badly.tf
    git commit -m "feat: add another bucket"
    git log --oneline

    Now pre-commit passes, the file is already clean. The commit is accepted.

    In real work the cycle is: commit → hook fix → re-add → commit. The cleaner the code the developer wrote, the less re-add you need.

    ✓ A clean commit passed the pre-commit gate.

  5. 05

    The validate hook blocks broken HCL

    Add a reference to a resource that does not exist:

    bash
    cat >> main.tf <<'EOF'
    output "broken" {
      value = aws_s3_bucket.does_not_exist.arn
    }
    EOF
    git add main.tf
    set +e
    git commit -m "feat: broken reference"
    code=$?
    set -e
    echo "exit: $code"

    It should fail on terraform_validate, a reference to a resource that does not exist. This is not a format issue that auto-fix handles; it is a semantic issue that needs a manual fix.

    Roll it back:

    bash
    git restore --staged main.tf
    sed -i '/output "broken"/,/^}$/d' main.tf
    cat main.tf

    ✓ validate rejected the broken HCL. Without pre-commit this would have reached plan in CI.

    The same thing on OpenTofu

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

    • → OpenTofu parity
  6. 06

    pre-commit run --all-files in CI

    Locally the hooks fire on CHANGED files. In CI you need to run everything, since old files may also break the policy (especially right after you adopt pre-commit in a legacy repo).

    bash
    pre-commit run --all-files
    echo "exit: $?"

    It should show passes. If someone committed by bypassing the hook (--no-verify), --all-files would catch it.

    This is the final CI step: pre-commit run --all-files, exit 1 on any problem.

    ✓ The pre-commit gate works. Next, functional tests.

    Hooks that did not make the base set

    What else is in antonbabenko/pre-commit-terraform and why:

    • terraform_docs, auto-generates README.md with inputs/outputs. Handy for modules; run it in CI with --args=--output-file so the README updates when variables change.
    • terraform_checkov, checkov inside pre-commit. Slow; usually CI only.
    • terraform_trivy, same idea as checkov, but trivy. Pick one of the two.
    • terraform_tfsec, deprecated, see tf-trivy-tfsec.
    • terragrunt_fmt / terragrunt_validate, if you have a terragrunt wrapper.
    • infracost_breakdown, cost estimation. Needs an API token.

    The rule: the slower a hook is, the less of it in local pre-commit and the more in CI. Locally you want checks under 5 seconds.

    • → pre-commit and CI
    • → Checkov for security scanning

Что ты узнал

pre-commit hooks into git via pre-commit install, reads .pre-commit-config.yaml, and runs the hooks on commit. The Terraform hooks come from antonbabenko/pre-commit-terraform: terraform_fmt, terraform_validate, terraform_tflint, terraform_docs.

команды

  • pre-commit installwire the hooks into .git/hooks. Once, right after clone.
  • pre-commit run --all-filesrun the whole repo. Use this to bootstrap an old project.
  • SKIP=terraform_fmt git commitskip one hook. Only on purpose.
  • pre-commit autoupdatebump the rev: versions in the config.

концепции

  • · terraform_fmt in a local hook, with auto-fix; in CI, fmt -check
  • · pre-commit and CI use one config, no drift between local and pipeline
  • · git commit --no-verify skips everything. Better not to.

← предыдущий

A Module from the Terraform Registry

следующий →

Your own Terraform provider, Go and the Plugin Framework

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