When developers talk about code quality, they usually mean application code, running ESLint, enforcing Prettier, and integrating static analysis into CI pipelines. But Infrastructure as Code (IaC) deserves the same treatment.
Terraform configurations and Ansible playbooks are real code. They define production environments, manage sensitive resources, and get reviewed in pull requests. Yet many teams apply zero linting or formatting standards to them, leading to inconsistent style, subtle misconfigurations, and security vulnerabilities that slip past reviewers.
This guide covers the essential tools and practices for linting and formatting Terraform and Ansible, and how to integrate them into your CI/CD pipeline for consistent, reviewable, production-grade infrastructure code.
Why Code Quality Matters for IaC
The consequences of poor-quality IaC are more severe than messy application code:
- Misconfigurations cause outages. A wrong variable type or missing validation in a Terraform module can destroy or misconfigure production infrastructure.
- Security vulnerabilities are harder to spot. Open security groups, overly permissive IAM roles, and unencrypted storage buckets often hide in unreviewed or inconsistently formatted configs.
- Drift compounds over time. Without enforced standards, IaC files written by different engineers diverge in style, making diffs harder to read and reviews less effective.
- Onboarding suffers. New team members struggle to understand inconsistently structured playbooks and modules.
Treating IaC with the same rigor as application code pays dividends in reliability and team velocity.
Linting and Formatting Terraform
Formatting with terraform fmt
Terraform ships with a built-in formatter. Running terraform fmt rewrites your .tf files to conform to the canonical HCL style, consistent indentation, aligned = signs, and normalized block structures.
# Format all .tf files recursively
terraform fmt -recursive
# Check formatting without writing changes (useful in CI)
terraform fmt -check -recursive
Make -check mode part of your CI pipeline. A non-zero exit code signals a formatting violation and fails the build, enforcing that all merged code is properly formatted.
Validation with terraform validate
Before linting, ensure your configuration is syntactically and semantically valid:
terraform init -backend=false
terraform validate
This catches undefined variables, incorrect resource references, and invalid argument types, without making any API calls to your cloud provider.
Deep Linting with TFLint
terraform validate only checks syntax. TFLint goes further, it analyzes your configurations against provider-specific rules, catches deprecated syntax, and flags likely bugs.
Install TFLint and initialize provider plugins:
brew install tflint # macOS
#or
curl -s https://raw.githubusercontent.com/terraform-linters/tflint/master/install_linux.sh | bash #linux
#or
choco install tflint #windows
#or
scoop install tflint #windows
#or
alias tflint="docker run --rm -v $(pwd):/data -t ghcr.io/terraform-linters/tflint" #docker
tflint --init
Configure it with a .tflint.hcl file in your repo root:
# .tflint.hcl
plugin "aws" {
enabled = true
version = "0.27.0"
source = "github.com/terraform-linters/tflint-ruleset-aws"
}
rule "terraform_naming_convention" {
enabled = true
}
rule "terraform_unused_declarations" {
enabled = true
}
Then run:
tflint --recursive
TFLint will flag issues like invalid instance types, deprecated resource arguments, and naming convention violations, all before any infrastructure is provisioned.
Security Scanning with Trivy or Checkov
Linting catches style and syntax issues. Security scanning catches misconfigurations and for IaC, this is critical.
Checkov is a static analysis tool purpose-built for IaC security:
pip install checkov
checkov -d . --framework terraform
It checks for issues like:
- S3 buckets with public access enabled
- Security groups open to
0.0.0.0/0 - Unencrypted RDS instances
- Missing CloudTrail logging
Trivy offers similar coverage and integrates well into container-based CI environments:
trivy config ./terraform
Run both as non-blocking checks early in adoption, then graduate them to hard failures as your team addresses existing violations.
Linting and Formatting Ansible
Linting with ansible-lint
ansible-lint is the standard linting tool for Ansible playbooks, roles, and task files. It enforces best practices defined by the Ansible community and catches common mistakes.
Install and run it:
pip install ansible-lint
ansible-lint playbooks/
Common issues it catches:
- Missing
nameon tasks (makes logs unreadable) - Use of deprecated modules
- Command and shell tasks that should use native Ansible modules
- Missing
becomeescalation where required - Incorrect YAML formatting
Configuring ansible-lint
Customize rules with a .ansible-lint file:
# .ansible-lint
warn_list:
- experimental
skip_list:
- yaml[line-length] # skip line length for long task names
exclude_paths:
- .cache/
- molecule/
You can also use profiles to enforce stricter rule sets as your team matures:
ansible-lint --profile production playbooks/
Profiles range from min (basic safety checks) to production (full best-practice enforcement).
Formatting YAML with Prettier or yamllint
Ansible playbooks are YAML and YAML is notoriously whitespace-sensitive. Enforce consistent formatting using yamllint:
pip install yamllint
yamllint playbooks/
Configure it with a .yamllint file:
# .yamllint
extends: default
rules:
line-length:
max: 120
indentation:
indent-sequences: consistent
truthy:
allowed-values: ['true', 'false']
For teams already using Prettier, it supports YAML formatting out of the box:
prettier --write "playbooks/**/*.yml"
Standardizing YAML formatting eliminates the most common source of noisy diff, whitespace-only changes.
Security Scanning Ansible with ansible-lint and Checkov
ansible-lint includes security-focused rules by default, flagging tasks that use shell or command with potentially unsafe inputs, or roles missing proper privilege escalation guards.
Checkov also supports Ansible:
checkov -d . --framework ansible
It flags hardcoded secrets, missing no-log directives on sensitive tasks, and overly permissive file permissions.
Integrating into CI/CD
All of these tools become most valuable when they run automatically on every pull request. Here's a sample GitHub Actions workflow covering both Terraform and Ansible:
# .github/workflows/iac-quality.yml
name: IaC Quality Checks
on: [pull_request]
jobs:
terraform:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- name: Terraform Format Check
run: terraform fmt -check -recursive
- name: Terraform Validate
run: |
terraform init -backend=false
terraform validate
- name: TFLint
uses: terraform-linters/setup-tflint@v4
with:
tflint_version: latest
- run: tflint --recursive
- name: Checkov Security Scan
uses: bridgecrewio/checkov-action@master
with:
directory: .
framework: terraform
ansible:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install tools
run: pip install ansible-lint yamllint
- name: yamllint
run: yamllint playbooks/
- name: ansible-lint
run: ansible-lint playbooks/
Start with warnings before enforcing hard failures — this gives teams time to remediate existing violations without blocking all merges on day one.
Recommended Toolchain Summary
| Tool | Purpose | Applies To |
|---|---|---|
terraform fmt |
Canonical formatting | Terraform |
terraform validate |
Syntax & semantic validation | Terraform |
| TFLint | Provider-aware deep linting | Terraform |
| Checkov | Security misconfiguration scanning | Terraform & Ansible |
| Trivy | Security scanning (container-friendly) | Terraform |
| ansible-lint | Best practice enforcement | Ansible |
| yamllint | YAML formatting & structure | Ansible |
| Prettier | YAML formatting (if already in stack) | Ansible |
Conclusion
Infrastructure as Code is not configuration, it's code, and it deserves the same quality standards your application code receives. By integrating terraform fmt, TFLint, Checkov, ansible-lint, and yamllint into your development workflow and CI pipeline, you catch misconfigurations before they reach production, enforce consistent standards across teams, and make infrastructure pull requests actually reviewable.
Start with formatting and basic validation, layer in security scanning, and enforce everything in CI. Your future self, and your on-call rotation, will thank you.
Already using a different IaC tool like Pulumi or OpenTofu? The same principles apply — drop a comment with your preferred linting setup.
Top comments (0)