Terraform formatting and validation: a step-by-step guide
As infrastructure code scales across teams and environments, inconsistent formatting and silent configuration errors become expensive problems. Terraform provides built-in tools to enforce code standards automatically and catch mistakes before they reach production. This guide walks through Terraform formatting and validation: a step-by-step guide, showing how to keep code clean automatically while integrating validation into your CI/CD pipeline.
Why formatting and validation matter
We've all seen Terraform repositories where indentation varies, variable naming conventions drift, and subtle errors slip through code review. The cost isn't just aesthetic—it's cognitive load during debugging, longer review cycles, and increased risk of deploying broken infrastructure. A team using consistent formatting and validation catches these issues before they propagate.
Terraform's native tools—fmt and validate—plus the community-driven TFLint handle different layers of code quality. Together, they form a safety net that scales with your codebase.
terraform fmt -recursive: enforcing consistent style
terraform fmt rewrites Terraform configuration files to a canonical format. It normalizes indentation, aligns attributes, and sorts arguments in a predictable order. The -recursive flag applies this across entire directory trees, making it ideal for monorepos and large deployments.
Run this once to see what would change without modifying files:
terraform fmt -recursive -check .The -check flag returns a non-zero exit code if reformatting is needed, perfect for CI checks. To apply formatting in place across all Terraform files in the current directory and subdirectories:
terraform fmt -recursive .A practical example: imagine a Terraform module for RDS clusters with inconsistent spacing:
resource "aws_rds_cluster" "main" {
cluster_identifier = "prod-db"
engine = "aurora-postgresql"
master_username = var.db_user
master_password=var.db_password
backup_retention_period = 7
}After running terraform fmt -recursive, it becomes:
resource "aws_rds_cluster" "main" {
cluster_identifier = "prod-db"
engine = "aurora-postgresql"
master_username = var.db_user
master_password = var.db_password
backup_retention_period = 7
}Notice the consistent indentation, aligned equals signs, and logical grouping. Teams can enforce this in pre-commit hooks or CI pipelines, removing the need for style debates in pull request reviews.
terraform validate: catching syntax and configuration errors
terraform validate checks whether configuration files are syntactically valid and internally consistent. It verifies that referenced resources, variables, and outputs exist, and that block types are recognized. Unlike fmt, validate doesn't modify files—it only reports problems.
Run validation in any Terraform working directory:
terraform validateA successful run outputs:
Success! The configuration is valid.When issues are found, validate reports line numbers and descriptions:
Error: Reference to undefined variable
on main.tf line 12, in resource "aws_instance" "app":
12: ami = var.nonexistent_ami
An input variable "nonexistent_ami" has not been declared.Consider a scenario where a developer adds a new security group but forgets to define the required variable:
resource "aws_security_group" "app" {
name = var.sg_name # This variable is not defined
}
variable "vpc_id" {
type = string
}Running terraform validate catches this before the code reaches a pipeline or review stage. The validation also checks for circular dependencies, duplicate resource names, and type mismatches in expressions.
Important note: validate does not verify that your AWS account has sufficient permissions, that resources don't already exist, or that your variable values are sensible. It's a syntax and structural check, not a full deployment simulation.
TFLint for provider-specific rules and best practices
While validate catches syntax errors, TFLint enforces style conventions, security best practices, and provider-specific rules that Terraform's native tools don't cover. It's community-maintained and highly configurable, supporting AWS, Azure, GCP, Kubernetes, and dozens of other providers.
Install TFLint (macOS example with Homebrew):
brew install tflintRun TFLint in a directory:
tflintA sample output showing common issues:
1 issue(s) found:
Notice: Missing version constraint for provider "aws" (terraform_required_providers)
on main.tf line 1
Warning: [aws_instance_invalid_type] instance_type is invalid
on main.tf line 8 in resource "aws_instance" "web"
8: instance_type = "t2.ultrasmall"
Error: [aws_security_group_invalid_ingress] Ingress rule should not allow unrestricted access
on main.tf line 15 in resource "aws_security_group" "allow_all"
15: cidr_blocks = ["0.0.0.0/0"]Configure TFLint with a .tflint.hcl file in your repository root to customize rules and set provider-specific behavior:
plugin "aws" {
enabled = true
version = "0.24.0"
source = "github.com/terraform-linters/tflint-ruleset-aws"
}
rule "terraform_required_version" {
enabled = true
}
rule "terraform_required_providers" {
enabled = true
}
rule "aws_instance_invalid_type" {
enabled = true
}
rule "aws_security_group_invalid_ingress" {
enabled = true
}A real-world example: a team writing an ECS task definition module runs TFLint and discovers that their log retention period is too short and their container image doesn't specify a tag (risking unexpected updates):
resource "aws_cloudwatch_log_group" "ecs" {
name = "/ecs/my-service"
retention_in_days = 1 # TFLint warns: too short for production
}
resource "aws_ecs_task_definition" "app" {
container_definitions = jsonencode([
{
image = "123456789.dkr.ecr.us-east-1.amazonaws.com/my-app" # TFLint warns: missing tag
}
])
}With these issues caught before deployment, the team avoids production surprises and security gaps.
Run validation in pre-commit and CI
Manual validation only works if developers remember to run it. Integrate validation into pre-commit hooks and CI pipelines to make it automatic and non-negotiable.
Pre-commit setup
Create a .pre-commit-config.yaml in your repository root:
repos:
- repo: https://github.com/terraform-linters/pre-commit-terraform
rev: v1.85.0
hooks:
- id: terraform_fmt
args: ['-recursive']
- id: terraform_validate
- id: tflintInstall pre-commit on your machine:
pip install pre-commitSet up the git hooks:
pre-commit installNow whenever a developer runs git commit, these checks run automatically. If any fail, the commit is blocked and output shows the issue.
CI pipeline integration
Add validation to your GitHub Actions, GitLab CI, or other CI system. A GitHub Actions example:
name: Terraform Validation
on:
pull_request:
paths:
- 'terraform/**'
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v2
with:
terraform_version: 1.7.0
- name: Terraform Format Check
run: terraform fmt -recursive -check terraform/
- name: Terraform Validate
run: terraform -chdir=terraform validate
- name: TFLint
uses: terraform-linters/setup-tflint@v4
- run: tflint --init
- run: tflint terraform/With this workflow, every pull request automatically validates formatting and correctness. Failed checks prevent merging, ensuring code quality standards are enforced consistently across the team.
Common mistakes
- Running
fmtbut notvalidate: Formatting only checks style, not correctness. A file can be perfectly formatted and still fail at plan time due to missing variables or invalid references. - Ignoring TFLint warnings: Many teams treat TFLint as optional, but its security and best-practice rules often catch subtle issues—like hardcoded credentials or overly permissive IAM policies—that
validatemisses. - Not committing
.tflint.hcl: If TFLint configuration varies across developer machines, the same code might pass locally but fail in CI, creating frustration and delays. - Skipping validation for generated code: Terraform files generated by tools, Terragrunt, or CDK still need validation. Many automation frameworks can skip validation steps, leading to hidden errors.
- Validating without a backend or vars file:
terraform validateworks without remote state or variable files, but it won't catch type mismatches between variable definitions and actual values used in resources.
Key takeaways
terraform fmt -recursivenormalizes formatting across your codebase, removing style inconsistencies and reducing cognitive load in code reviews.terraform validatecatches syntax errors and structural problems—missing variables, invalid references, circular dependencies—before they reach production.- TFLint enforces provider-specific best practices, security rules, and conventions that native Terraform tools don't cover, catching issues like unrestricted ingress rules or missing resource tags.
- Pre-commit hooks and CI pipelines make validation automatic and non-optional, scaling code quality enforcement across your entire team.
- Combine all three tools in a cohesive workflow:
fmtfor style,validatefor syntax, and TFLint for best practices.






Comments
Loading comments…