DEV Community

ThankGod Chibugwum Obobo
ThankGod Chibugwum Obobo

Posted on • Originally published at actocodes.hashnode.dev

Infrastructure as Code Is Still Code: How to Lint and Format Terraform and Ansible

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

Then run:

tflint --recursive
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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/
Enter fullscreen mode Exit fullscreen mode

Common issues it catches:

  • Missing name on tasks (makes logs unreadable)
  • Use of deprecated modules
  • Command and shell tasks that should use native Ansible modules
  • Missing become escalation 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/
Enter fullscreen mode Exit fullscreen mode

You can also use profiles to enforce stricter rule sets as your team matures:

ansible-lint --profile production playbooks/
Enter fullscreen mode Exit fullscreen mode

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/
Enter fullscreen mode Exit fullscreen mode

Configure it with a .yamllint file:

# .yamllint
extends: default
rules:
  line-length:
    max: 120
  indentation:
    indent-sequences: consistent
  truthy:
    allowed-values: ['true', 'false']
Enter fullscreen mode Exit fullscreen mode

For teams already using Prettier, it supports YAML formatting out of the box:

prettier --write "playbooks/**/*.yml"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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/
Enter fullscreen mode Exit fullscreen mode

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)