This chapter follows HCL structure in Terraform. Treat formatting and validation as part of every change, locally and in CI, before anyone approves an apply.
terraform fmt
terraform fmt rewrites .tf and .tfvars files to HashiCorp’s canonical style (indentation, alignment, and spacing). The formatter is built into Terraform; it does not shell out to gofmt.
terraform fmt -recursiveUseful flags:
| Flag | Effect |
|---|---|
-check |
Exit with code 1 if files would change (CI-friendly) |
-diff |
Print a diff when formatting would change files |
-recursive |
Walk subdirectories |
Run fmt before every commit. In CI, terraform fmt -check -recursive fails the job when style drifts.
terraform validate
terraform validate checks configuration syntax and internal consistency (unknown arguments, wrong types, invalid references). It does not call cloud APIs and does not prove your credentials work.
Validation requires an initialized working directory:
terraform init -backend=falseterraform validate-backend=false skips remote backend setup when you only want a fast syntax pass in CI.
Fix reported errors before terraform plan. Validate catches many issues fmt cannot, such as referencing a resource that does not exist in the module.
Variable and resource checks
Variable validation (in variable blocks) rejects invalid input at plan time:
variable "machine_type" { type = string
validation { condition = contains(["e2-micro", "e2-small"], var.machine_type) error_message = "Tutorial VMs must use e2-micro or e2-small." }}Preconditions and postconditions on resources (Terraform 1.5+) assert rules during plan and apply:
resource "google_storage_bucket" "example" { name = var.bucket_name location = var.region
lifecycle { precondition { condition = length(var.bucket_name) >= 3 error_message = "Bucket name must be at least three characters." } }}Check blocks (Terraform 1.5+) run assertions that can report warnings without failing the run, useful for policy-style feedback. Use them when a violation should surface in plan output but not always block apply—team policy decides.
Plan as the last gate
Even with fmt and validate green, terraform plan remains the authoritative preview of API-level changes. Review creates, updates, replaces, and deletes. For production, store plans with -out=plan.tfplan and apply that file in a later step so CI and humans apply exactly what was reviewed.
terraform plan -refresh-only updates state from the cloud without proposing resource changes—handy for drift inspection, covered further in State data in Terraform.
TFLint and provider rules
TFLint adds lint rules beyond validate: deprecated arguments, naming conventions, and provider-specific checks. For Google Cloud, enable the Google ruleset plugin and run it after init:
tflint --inittflintTFLint can fail on issues validate misses, such as invalid machine types for a region. Keep TFLint version and plugins pinned in CI like Terraform itself.
Pre-commit and CI
A minimal pre-commit hook runs fmt and validate on changed directories:
terraform fmt -check -recursiveterraform init -backend=falseterraform validateIn CI, run the same steps on every pull request that touches .tf files. Optionally add TFLint and terraform fmt -check in parallel jobs.
Example GitHub Actions shape (adapt paths and Terraform version):
jobs: validate: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: hashicorp/setup-terraform@v3 - run: terraform fmt -check -recursive - run: terraform init -backend=false working-directory: infra/envs/dev - run: terraform validate working-directory: infra/envs/devterraform test
For reusable modules, terraform test (.tftest.hcl files) runs apply/destroy or mock-based scenarios against real or mocked providers. It fits module maintainers validating regressions after refactors—see HashiCorp’s documentation when you outgrow fmt, validate, and plan alone.
Next: Terraform modules shows how to package and reuse configuration.
Check your understanding
Question 1 of 7
0 of 7 checked