Infracost Cheat Sheet
Overview
Infracost is a cloud cost estimation tool that integrates with Terraform and OpenTofu to show how infrastructure changes will impact monthly cloud spending before they are applied. It parses HCL code and Terraform plan files to generate detailed cost breakdowns for AWS, Azure, and Google Cloud resources, enabling engineers to make cost-conscious decisions during the development process rather than discovering cost surprises after deployment.
Infracost integrates natively into CI/CD pipelines and pull request workflows, posting cost difference comments directly on GitHub, GitLab, Bitbucket, and Azure DevOps pull requests. This shift-left approach to FinOps gives teams immediate visibility into the financial impact of infrastructure changes. The tool supports usage-based cost estimation, custom price books, organization-wide policies, and tagging governance through its cloud dashboard.
Installation
CLI Installation
# macOS
brew install infracost
# Linux
curl -fsSL https://raw.githubusercontent.com/infracost/infracost/master/scripts/install.sh | sh
# Windows (Scoop)
scoop install infracost
# Docker
docker pull infracost/infracost:latest
# Verify installation
infracost --version
# Register and authenticate
infracost auth login
# Or set API key manually
export INFRACOST_API_KEY="your-api-key"
CI/CD Setup
# GitHub Actions
# Add INFRACOST_API_KEY to repository secrets
# GitLab CI
# Add INFRACOST_API_KEY to CI/CD variables
# Verify API key
infracost configure get api_key
# Set default currency
infracost configure set currency USD
# Set pricing API endpoint (self-hosted)
infracost configure set pricing_api_endpoint https://pricing.internal.com
Core Commands
Basic Cost Estimation
# Generate cost breakdown for a Terraform directory
infracost breakdown --path /path/to/terraform
# Generate from a Terraform plan file
terraform plan -out=plan.tfplan
terraform show -json plan.tfplan > plan.json
infracost breakdown --path plan.json
# Output in different formats
infracost breakdown --path . --format json > costs.json
infracost breakdown --path . --format table
infracost breakdown --path . --format html > costs.html
# Show monthly cost breakdown with usage
infracost breakdown --path . --show-skipped
# Filter by specific Terraform var file
infracost breakdown --path . --terraform-var-file="production.tfvars"
# Pass Terraform variables
infracost breakdown --path . --terraform-var="instance_type=m5.xlarge"
Cost Diff (Comparing Changes)
# Compare current branch to main
infracost diff --path . --compare-to infracost-base.json
# Generate baseline for comparison
git checkout main
infracost breakdown --path . --format json --out-file infracost-base.json
git checkout feature-branch
infracost diff --path . --compare-to infracost-base.json
# Diff with percentage threshold
infracost diff --path . --compare-to infracost-base.json \
--format json --out-file infracost-diff.json
Multi-Project Workspaces
# Create infracost config file for monorepo
cat > infracost.yml << 'EOF'
version: 0.1
projects:
- path: terraform/production
name: production
terraform_var_files:
- production.tfvars
- path: terraform/staging
name: staging
terraform_var_files:
- staging.tfvars
- path: terraform/networking
name: networking
- path: modules/database
name: database-module
terraform_vars:
instance_class: db.r6g.xlarge
multi_az: true
EOF
# Run with config file
infracost breakdown --config-file infracost.yml
# Generate diff for all projects
infracost diff --config-file infracost.yml --compare-to baseline.json
Configuration
Usage File (Estimate Usage-Based Costs)
# infracost-usage.yml
version: 0.1
resource_usage:
# AWS Lambda
aws_lambda_function.api:
monthly_requests: 10000000
request_duration_ms: 250
# AWS S3
aws_s3_bucket.data:
standard:
storage_gb: 5000
monthly_tier_1_requests: 1000000
monthly_tier_2_requests: 5000000
glacier:
storage_gb: 50000
# AWS NAT Gateway
aws_nat_gateway.main:
monthly_data_processed_gb: 500
# AWS CloudWatch
aws_cloudwatch_log_group.app:
monthly_data_ingested_gb: 100
monthly_data_scanned_gb: 50
# AWS RDS
aws_db_instance.primary:
monthly_additional_backup_storage_gb: 200
# AWS ELB
aws_lb.main:
monthly_data_processed_gb: 1000
new_connections: 100000
active_connections: 5000
rule_evaluations: 500000
# Resource type defaults (apply to all of this type)
aws_instance["*"]:
monthly_hrs: 730 # full month
# Use the usage file
infracost breakdown --path . --usage-file infracost-usage.yml
# Generate usage template from terraform
infracost breakdown --path . --sync-usage-file --usage-file infracost-usage.yml
GitHub Actions Integration
# .github/workflows/infracost.yml
name: Infracost
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: Checkout base branch
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.base.ref }}
path: base
- name: Generate base cost
run: infracost breakdown --path base/terraform --format json --out-file /tmp/base.json
- name: Checkout PR branch
uses: actions/checkout@v4
- name: Generate diff
run: infracost diff --path terraform --compare-to /tmp/base.json --format json --out-file /tmp/diff.json
- name: Post PR comment
run: |
infracost comment github \
--path /tmp/diff.json \
--repo $GITHUB_REPOSITORY \
--github-token ${{ secrets.GITHUB_TOKEN }} \
--pull-request ${{ github.event.pull_request.number }} \
--behavior update
GitLab CI Integration
# .gitlab-ci.yml
infracost:
image: infracost/infracost:latest
stage: plan
script:
- git clone $CI_REPOSITORY_URL --branch $CI_MERGE_REQUEST_TARGET_BRANCH_NAME --single-branch base
- infracost breakdown --path base/terraform --format json --out-file /tmp/base.json
- infracost diff --path terraform --compare-to /tmp/base.json --format json --out-file /tmp/diff.json
- infracost comment gitlab
--path /tmp/diff.json
--repo $CI_PROJECT_PATH
--merge-request $CI_MERGE_REQUEST_IID
--gitlab-server-url $CI_SERVER_URL
--gitlab-token $GITLAB_TOKEN
--behavior update
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
changes:
- terraform/**/*
Advanced Usage
Cost Policies
# Create policy file (OPA/Rego)
cat > policy.rego << 'EOF'
package infracost
deny[msg] {
maxMonthlyCost := 5000.0
msg := sprintf(
"Total monthly cost $%.2f exceeds maximum $%.2f",
[to_number(input.totalMonthlyCost), maxMonthlyCost]
)
to_number(input.totalMonthlyCost) > maxMonthlyCost
}
deny[msg] {
r := input.projects[_].breakdown.resources[_]
r.name == "aws_instance"
contains(r.metadata["instanceType"], "x2")
msg := sprintf("Resource %s uses expensive x2 instance type", [r.name])
}
EOF
# Evaluate policies
infracost breakdown --path . --format json | infracost policy eval --policy-file policy.rego
# Use in CI with exit code
infracost breakdown --path . --format json > costs.json
if infracost policy eval --policy-file policy.rego --path costs.json; then
echo "Cost policy passed"
else
echo "Cost policy violated"
exit 1
fi
Terraform Cloud/Enterprise Integration
# Use with Terraform Cloud remote state
export INFRACOST_TERRAFORM_CLOUD_TOKEN="your-tfc-token"
export INFRACOST_TERRAFORM_CLOUD_HOST="app.terraform.io"
infracost breakdown --path . \
--terraform-workspace production
# With Terraform Enterprise
export INFRACOST_TERRAFORM_CLOUD_HOST="tfe.company.com"
infracost breakdown --path .
Bulk Estimation and Reporting
# Generate costs for all workspaces
for dir in terraform/*/; do
name=$(basename "$dir")
infracost breakdown --path "$dir" \
--format json \
--out-file "/tmp/infracost-${name}.json"
done
# Combine multiple outputs
infracost output \
--path "/tmp/infracost-*.json" \
--format table
# Generate HTML report
infracost output \
--path "/tmp/infracost-*.json" \
--format html \
--out-file cost-report.html
# Upload to Infracost Cloud dashboard
infracost upload --path /tmp/infracost-production.json
Terragrunt Support
# Single Terragrunt module
infracost breakdown --path /path/to/terragrunt/module
# All Terragrunt modules in a directory
infracost breakdown --path /path/to/terragrunt \
--terraform-binary terragrunt \
--terraform-workspace default
Troubleshooting
| Issue | Cause | Solution |
|---|---|---|
| Resources showing $0 | Usage-based pricing not estimated | Add usage file with expected consumption |
Error: No valid Terraform files found | Wrong path or missing .tf files | Verify —path points to directory with .tf files |
| Slow execution | Large Terraform state | Use plan JSON instead of HCL parsing |
| Missing resources in output | Resource type not supported | Check supported resources at infracost.io/docs/supported_resources |
| API rate limit exceeded | Too many parallel runs | Use --no-cache sparingly; batch CI runs |
| Incorrect region pricing | Provider region not detected | Set provider_region_override in config |
| Terraform init failing | Missing backend credentials | Set required env vars or use --terraform-init-flags |
| PR comment not posting | Token permissions insufficient | Ensure GitHub token has pull-requests: write |
# Debug: show detailed logs
INFRACOST_LOG_LEVEL=debug infracost breakdown --path .
# Debug: show which resources are supported
infracost breakdown --path . --show-skipped --format json | jq '.summary'
# Check configured settings
infracost configure get api_key
infracost configure get currency
infracost configure get pricing_api_endpoint
# Clear cache
rm -rf ~/.config/infracost/.cache
# Validate config file
infracost breakdown --config-file infracost.yml --log-level debug 2>&1 | head -20