Introduction
Terraform has become the standard for infrastructure as code, but managing Terraform at scale requires additional tools. Terragrunt reduces configuration duplication. Terratest enables automated infrastructure testing. Tfsec scans configurations for security issues. Infracost provides cost estimates before deployment. This article covers each tool with practical examples.

Terragrunt
A thin wrapper that keeps Terraform configurations DRY:
terragrunt.hcl (root configuration)
remote_state {
backend = "s3"
config = {
bucket = "my-company-terraform-state"
key = "${path_relative_to_include()}/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
generate "provider" {
path = "provider.tf"
if_exists = "overwrite_terragrunt"
contents = <
provider "aws" {
region = local.aws_region
default_tags {
tags = {
Environment = local.environment
ManagedBy = "terragrunt"
}
}
}
EOF
}
locals {
aws_region = "us-east-1"
environment = reverse(split("/", get_terragrunt_dir()))[1]
}
dev/vpc/terragrunt.hcl
terraform {
source = "../../modules/vpc"
}
include "root" {
path = find_in_parent_folders()
}
inputs = {
vpc_name = "dev-vpc"
cidr_block = "10.0.0.0/16"
enable_nat = true
enable_vpn = false
private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
}
Apply all modules in dependency order
terragrunt run-all apply
Plan only changed modules
terragrunt run-all plan
Output dependency graph
terragrunt graph | dot -Tpng > graph.png
Validate all configurations
terragrunt run-all validate
Destroy with confirmation
terragrunt run-all destroy
Terratest
Go library for writing automated infrastructure tests:
package test
import (
"testing"
"github.com/gruntwork-io/terratest/modules/terraform"
"github.com/gruntwork-io/terratest/modules/aws"
"github.com/stretchr/testify/assert"
)
func TestVPCDeployment(t *testing.T) {
terraformOptions := &terraform.Options;{
TerraformDir: "../examples/vpc",
Vars: map[string]interface{}{
"vpc_name": "test-vpc",
"cidr_block": "10.0.0.0/16",
},
NoColor: true,
}
// Clean up at the end
defer terraform.Destroy(t, terraformOptions)
// Deploy infrastructure
terraform.InitAndApply(t, terraformOptions)
// Verify outputs
vpcID := terraform.Output(t, terraformOptions, "vpc_id")
assert.NotEmpty(t, vpcID)
// Verify the VPC actually exists in AWS
vpc := aws.GetVpcById(t, vpcID, "us-east-1")
assert.Equal(t, "10.0.0.0/16", vpc.CidrBlock)
// Verify subnets were created
subnets := aws.GetSubnetsForVpc(t, vpcID, "us-east-1")
assert.Len(t, subnets, 4)
// Verify security group rules
sgID := terraform.Output(t, terraformOptions, "web_sg_id")
sgRules := aws.GetSecurityGroupRules(t, sgID, "us-east-1")
assert.GreaterOrEqual(t, len(sgRules), 2)
}
tfsec (now Trivy)
Security scanning for Terraform configurations:
Install tfsec
brew install tfsec
Or use Trivy which includes tfsec rules
brew install trivy
Scan Terraform files
tfsec .
tfsec ./environments/production
tfsec --no-color --format json > scan-results.json
In CI
tfsec --soft-fail # Don't fail CI, just report
Trivy equivalent
trivy config --severity HIGH,CRITICAL .
Bad configuration that tfsec catches
resource "aws_s3_bucket" "data" {
bucket = "my-data-bucket"
acl = "public-read" # tfsec: aws-s3-no-public-read-acl
}
resource "aws_security_group" "web" {
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"] # tfsec: aws-vpc-no-public-ingress-sgr
}
}
Fixed configuration
resource "aws_s3_bucket" "data" {
bucket = "my-data-bucket"
acl = "private"
}
resource "aws_security_group" "web" {
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["10.0.0.0/8"] # Restricted to internal network
}
}
Infracost
Cost estimation for Terraform projects:
Install
brew install infracost
infracost auth login
Generate cost estimate for current state
infracost breakdown --path .
Compare with previous state
infracost diff --path . --compare-to previous-cost.json
Output in different formats
infracost breakdown --path . --format json
infracost breakdown --path . --format html --out-file cost-report.html
In CI
infracost diff --path . --show-skipped
.infracost.yml — usage estimates
version: 0.1
projects:
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\- path: ./environments/production
usage:
aws_instance.web:
monthly_cpu_credit_hrs: 100
monthly_hrs: 730
aws_lambda.function:
monthly_requests: 1000000
request_duration_ms: 500
aws_s3_bucket.data:
storage_gb: 500
monthly_tier_1_requests: 10000
Other Essential Tools
pre-commit hooks for Terraform:
.pre-commit-config.yaml
repos:
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\- repo: https://github.com/antonbabenko/pre-commit-terraform
rev: v1.96.0
hooks:
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\- id: terraform_fmt
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\- id: terraform_validate
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\- id: terraform_tflint
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\- id: terraform_trivy
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\- id: terraform_docs
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\- id: checkov
Comparison
| Tool | Purpose | Essential For |
|------|---------|---------------|
| Terragrunt | DRY configuration | Multi-environment, multi-module projects |
| Terratest | Infrastructure testing | Automated validation of deployments |
| tfsec/Trivy | Security scanning | Catching misconfigurations before deploy |
| Infracost | Cost estimation | Budget planning and cost awareness |
| pre-commit | Code quality | Consistent formatting and validation |
Recommendations
-
Multi-environment IaC : Use Terragrunt to eliminate configuration duplication across environments.
-
Production safety : Use Terratest to write automated tests that verify infrastructure after deployment.
-
Security compliance : Run tfsec or Trivy in CI pipelines to catch misconfigurations before merge.
-
Cost awareness : Use Infracost in PR pipelines to show cost implications of infrastructure changes.
-
Code quality : Set up pre-commit hooks for terraform fmt, validate, tflint, and docs.
The ideal Terraform workflow combines all these tools: Terragrunt for structure, pre-commit for quality, tfssec for security, Infracost for cost awareness, and Terratest for deployment validation.
Enjoy this article? Share your thoughts, questions, or experiences in the comments below — your insights help other readers too.
Join the discussion ↓