TL;DR
- Infracost shows cost impact of Terraform changes in every PR — before deployment, not after billing
- Cost policies can block PRs that exceed thresholds (like security scanners block vulnerabilities)
- The #1 mistake: treating cost reviews as optional instead of automated gates
Best for: Teams with significant cloud spend or those who’ve been surprised by bills Skip if: You’re on free tier or fixed-price infrastructure Read time: 9 minutes
Your Terraform PR looks good. Tests pass, security scan clean, approved by two reviewers. You merge. Next month’s AWS bill arrives: $47,000 over budget. Someone added a db.r6g.4xlarge RDS instance where db.t3.medium would’ve worked.
This is the FinOps gap — infrastructure decisions made without cost visibility. In 2026, cost estimation belongs in CI alongside security scanning. You shouldn’t deploy what you can’t afford.
The Real Problem
Cloud costs fail to stay predictable because cost feedback comes too late. The billing cycle is monthly. By the time you see the impact, the resources have been running for weeks.
Traditional cost management approaches:
- Budget alerts that fire after you’ve overspent
- Monthly cost reviews that happen after resources are deployed
- Manual estimates that are wrong or skipped entirely
The shift-left approach for costs:
- Every PR shows estimated monthly cost change
- Cost policies block changes exceeding thresholds
- Cost attribution happens at deploy time, not month-end
This isn’t about penny-pinching — it’s about making cost a first-class engineering concern alongside performance and security.
Infracost: The Standard Tool
Infracost has become the de facto standard for Terraform cost estimation. It parses your Terraform files, queries cloud pricing APIs, and outputs cost breakdowns.
# Install
brew install infracost
# Authenticate (free tier available)
infracost auth login
# Basic cost breakdown
infracost breakdown --path ./terraform
# Compare two states (for PR diffs)
infracost diff --path ./terraform --compare-to infracost-base.json
The output shows both existing and new resource costs:
Project: terraform
Name Monthly Qty Unit Monthly Cost
aws_instance.web
├─ Instance usage (Linux/UNIX, on-demand, t3.large)
│ 730 hours $60.74
└─ root_block_device
└─ Storage (gp3) 50 GB $4.00
aws_rds_instance.database
├─ Database instance (on-demand, db.r6g.4xlarge)
│ 730 hours $1,752.40
└─ Storage (gp2) 100 GB $11.50
OVERALL TOTAL $1,828.64
The key insight: you see exact pricing before terraform apply.
CI/CD Integration
Infracost shines when integrated into PR workflows. Here’s a GitHub Actions setup:
name: Cost Estimation
on:
pull_request:
paths:
- 'terraform/**'
jobs:
infracost:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v4
- name: Setup Infracost
uses: infracost/actions/setup@v3
with:
api-key: ${{ secrets.INFRACOST_API_KEY }}
- name: Generate Infracost baseline
run: |
infracost breakdown --path=terraform \
--format=json \
--out-file=/tmp/infracost-base.json
env:
INFRACOST_API_KEY: ${{ secrets.INFRACOST_API_KEY }}
- name: Generate Infracost diff
run: |
infracost diff --path=terraform \
--format=json \
--compare-to=/tmp/infracost-base.json \
--out-file=/tmp/infracost-diff.json
- name: Post PR comment
uses: infracost/actions/comment@v1
with:
path: /tmp/infracost-diff.json
behavior: update
This posts a comment on every PR showing:
- Current monthly cost
- Cost after changes
- Diff (increase or decrease)
- Per-resource breakdown
Cost Policies
Visibility is step one. Step two is enforcement. Infracost supports policy-as-code for costs:
# infracost-policy.yml
version: 0.1
policies:
- policy_path: policies/cost-policy.rego
rego_url: https://raw.githubusercontent.com/infracost/infracost/master/examples/opa/policies/cost-policy.rego
Write cost policies in Rego (same as OPA):
package infracost
# Block PRs that increase monthly cost by more than $500
deny[msg] {
maxDiff := 500.0
input.diffTotalMonthlyCost > maxDiff
msg := sprintf("Monthly cost increase ($%.2f) exceeds maximum allowed ($%.2f)", [
input.diffTotalMonthlyCost,
maxDiff
])
}
# Require approval for changes over $100
warn[msg] {
input.diffTotalMonthlyCost > 100
input.diffTotalMonthlyCost <= 500
msg := sprintf("Cost increase of $%.2f requires finance team approval", [
input.diffTotalMonthlyCost
])
}
# Block specific expensive instance types
deny[msg] {
resource := input.projects[_].breakdown.resources[_]
contains(resource.name, "aws_instance")
contains(resource.metadata.instance_type, "x2idn")
msg := sprintf("Instance type x2idn not allowed without exception. Resource: %s", [resource.name])
}
Run policy evaluation in CI:
infracost breakdown --path ./terraform --format json | \
infracost policy --policy-path ./policies/
Exit code 1 means policy violation — block the PR.
Tagging for Cost Attribution
Cost estimation works best with proper tagging. Enforce tags that enable cost attribution:
# variables.tf
variable "cost_center" {
description = "Cost center for billing attribution"
type = string
validation {
condition = can(regex("^CC-[0-9]{4}$", var.cost_center))
error_message = "Cost center must match format CC-XXXX"
}
}
variable "environment" {
description = "Environment (dev/staging/prod)"
type = string
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Environment must be dev, staging, or prod"
}
}
# main.tf
locals {
common_tags = {
CostCenter = var.cost_center
Environment = var.environment
ManagedBy = "terraform"
Project = var.project_name
}
}
resource "aws_instance" "web" {
# ...
tags = merge(local.common_tags, {
Name = "web-server"
})
}
Combine with Compliance Testing to enforce tagging policies.
Handling Usage-Based Costs
Infracost estimates based on provisioned resources, but many cloud costs depend on usage. For accurate estimates, provide usage data:
# infracost-usage.yml
version: 0.1
resource_usage:
aws_lambda_function.api:
monthly_requests: 10000000
request_duration_ms: 250
aws_s3_bucket.data:
standard:
storage_gb: 500
monthly_tier_1_requests: 100000
monthly_tier_2_requests: 1000000
aws_dynamodb_table.users:
monthly_write_request_units: 5000000
monthly_read_request_units: 25000000
Run with usage file:
infracost breakdown --path ./terraform --usage-file infracost-usage.yml
For production workloads, generate usage from actual CloudWatch metrics:
# Export usage from running infrastructure
infracost breakdown --path ./terraform --sync-usage-file infracost-usage.yml
Multi-Environment Cost Views
Most teams deploy to multiple environments. Structure Infracost runs to show per-environment costs:
# .github/workflows/cost-estimation.yml
jobs:
infracost:
strategy:
matrix:
environment: [dev, staging, prod]
steps:
- name: Infracost for ${{ matrix.environment }}
run: |
infracost breakdown \
--path=terraform/environments/${{ matrix.environment }} \
--format=json \
--out-file=/tmp/infracost-${{ matrix.environment }}.json
Aggregate for total cost view:
infracost output --path="/tmp/infracost-*.json" --format=table
AI-Assisted Approaches
Cost optimization involves understanding both pricing models and usage patterns. AI tools accelerate this.
What AI does well:
- Suggesting cheaper instance types for workloads
- Identifying Reserved Instance or Savings Plan opportunities
- Explaining pricing models for unfamiliar services
- Generating Infracost usage files from requirements
What still needs humans:
- Deciding acceptable cost vs performance tradeoffs
- Understanding business context for cost allocation
- Approving exceptions for expensive resources
- Validating AI-suggested optimizations actually work
Useful prompt:
Review this Terraform configuration and suggest cost optimizations:
- Current: db.r6g.4xlarge RDS instance
- Workload: 500 QPS read, 50 QPS write, 100GB storage
- Constraints: Must maintain <10ms p99 latency
Provide alternative instance types with estimated monthly savings
When This Breaks Down
Cost estimation has limitations:
Pricing accuracy: Cloud providers change prices. Free tier limits vary by account age. Committed use discounts aren’t reflected in raw estimates.
Usage estimation: Serverless and usage-based services require usage predictions. Lambda, S3 requests, data transfer — all depend on traffic you might not know in advance.
Cross-resource costs: Data transfer between resources (VPC endpoints, cross-region replication) often dominates bills but is hard to estimate from Terraform alone.
Spot/preemptible pricing: If you use spot instances, actual costs vary significantly from on-demand estimates.
Consider complementary approaches:
- AWS Cost Explorer / Azure Cost Management for historical analysis
- Cloud provider budgets for real-time alerts
- Drift Detection to catch manually-created expensive resources
Decision Framework
Enable Infracost on all PRs when:
- Cloud bill exceeds $10K/month
- Multiple teams deploy independently
- You’ve had cost surprises in the past year
- Engineers don’t know cloud pricing
Add cost policies when:
- Specific resource types need approval
- Budget limits are hard constraints
- Teams request exceptions frequently
- Cost increases correlate with incidents (over-provisioning as “fix”)
Skip cost estimation when:
- Fixed-price contracts cover infrastructure
- Development-only environments with auto-shutdown
- Greenfield projects where scale is unknown
Measuring Success
| Metric | Before | After | How to Track |
|---|---|---|---|
| Cost surprises per quarter | Unknown | 0 | Finance escalations |
| PR-to-bill correlation | Manual | Automated | Infracost vs actual |
| Time to identify cost spike cause | Days | Minutes | Incident response logs |
| Over-provisioned resources | Unknown | <10% | Right-sizing reports |
Warning signs it’s not working:
- Teams ignoring cost comments
- Too many policy exceptions requested
- Estimates consistently wrong vs actual bills
- Cost reviews happening manually despite automation
What’s Next
Start with visibility, then add enforcement:
- Enable Infracost on one repository
- Run for 2 sprints without policies (gather baseline)
- Identify reasonable thresholds from historical data
- Add warning policies first (no blocking)
- Graduate to blocking policies for clear violations
- Expand to additional repositories
The goal is making cost part of the definition of “done” — a change isn’t complete until you know what it costs.
Related articles:
- Compliance Testing for IaC
- Policy as Code Testing: OPA vs Sentinel
- Drift Detection in Infrastructure
- Infrastructure as Code Testing
External resources: