Cost Management

Cloud Cost Estimation with Infracost: How It Works and What It Covers

By Raghvendra Pandey · June 2026 · 10 min read

Cloud infrastructure bills are notoriously hard to predict. A Terraform change that looks like a small configuration tweak — upgrading an RDS instance class, enabling Multi-AZ, adding a NAT gateway — can add hundreds of dollars per month. Without tooling, engineers discover the cost impact after the change is deployed and the first bill arrives.

Infracost bridges the gap between infrastructure code and cost. It reads your Terraform (or Terragrunt) and queries cloud provider pricing APIs to generate a cost breakdown before anything is deployed. This guide covers how Infracost works, what it can and can't estimate, how to run it, and how to wire it into a CI/CD pipeline so cost changes appear on every pull request.

How Infracost works

Infracost has three main phases:

1. Parse the Terraform project. Infracost runs terraform init and terraform plan internally, generating a plan JSON file. It reads resource types, instance types, storage sizes, replication settings, and other cost-relevant attributes from the plan output.

2. Look up pricing. Infracost maintains a pricing database synced from AWS, GCP, and Azure price lists. It queries this database to find the on-demand price for each resource configuration. For usage-based resources (data transfer, Lambda invocations, DynamoDB read/write units), it applies usage estimates from a infracost-usage.yml file or uses zero as a baseline.

3. Generate the output. The result is a breakdown of monthly costs per resource, organized by module and resource type, plus a diff view when comparing two Terraform states.

$ infracost breakdown --path .

Name                                      Monthly Qty  Unit          Monthly Cost

aws_instance.web
  Instance usage (Linux/UNIX, on-demand)          730  hours              $29.20
  root_block_device
    Storage (general purpose SSD, gp3)             50  GB                  $4.00

aws_db_instance.main
  Database instance (db.t3.medium, Multi-AZ)      730  hours             $118.26
  Storage (general purpose SSD, gp2)              100  GB                 $23.00

aws_nat_gateway.main
  NAT gateway                                      730  hours              $32.85
  Data processed                                     0  GB                  $0.00

OVERALL TOTAL                                                             $207.31

What Infracost can estimate

Infracost covers most compute, storage, and database resources where pricing is instance-type or size based:

What Infracost cannot estimate

Some costs are unknowable from Terraform configuration alone because they depend entirely on runtime behavior:

Infracost marks these as $0.00 with a note that usage estimates are needed, which is accurate — zero is the floor, not the expected cost.

Usage estimates

For usage-based resources, Infracost reads a infracost-usage.yml file where you specify expected usage:

# infracost-usage.yml
version: 0.1

resource_usage:
  aws_lambda_function.api:
    monthly_requests: 5000000       # 5M invocations/month
    request_duration_ms: 200        # average 200ms per invocation
    memory_mb: 512

  aws_cloudfront_distribution.cdn:
    monthly_data_transfer_to_internet_gb: 500

  aws_nat_gateway.main:
    monthly_data_processed_gb: 100

  aws_dynamodb_table.sessions:
    monthly_read_request_units: 1000000
    monthly_write_request_units: 200000
infracost breakdown --path . --usage-file infracost-usage.yml

Usage estimates are approximate by definition, but even rough estimates make cost modeling far more useful than showing $0.00 for all usage-based resources. Teams often store a shared infracost-usage.yml alongside production usage metrics, updated quarterly.

Running Infracost

Install

# macOS
brew install infracost

# Linux
curl -fsSL https://raw.githubusercontent.com/infracost/infracost/master/scripts/install.sh | sh

# Register for a free API key (rate limiting, no data stored)
infracost auth login

Basic cost breakdown

# Show costs for current Terraform directory
infracost breakdown --path .

# Output JSON for programmatic use
infracost breakdown --path . --format json --out-file infracost.json

# Show a diff between two Terraform states
infracost diff --path . --compare-to infracost-base.json

Cost diff between branches

The diff workflow is the most useful for pull request review: compare the base branch cost against the PR branch cost and surface only the delta.

# On the base branch, generate baseline
git checkout main
infracost breakdown --path . --format json --out-file infracost-base.json

# On the PR branch, generate diff
git checkout feature/upgrade-rds
infracost diff --path . --compare-to infracost-base.json
Project: my-infrastructure

+ aws_db_instance.main
  + Database instance class changed: db.t3.medium to db.r6g.large
    +$168.44 (+142%)

~ aws_instance.web (no change)
~ aws_nat_gateway.main (no change)

Monthly cost change: +$168.44 (from $207.31 to $375.75)

CI/CD integration

Infracost's most valuable feature is the PR comment: on every pull request that changes Terraform, Infracost posts a cost diff directly in the PR. Engineers see the impact before merging.

# .github/workflows/infracost.yml
name: Infracost Cost Estimate
on: [pull_request]

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 cost estimate for base branch
        run: |
          git fetch origin ${{ github.base_ref }}
          git checkout origin/${{ github.base_ref }}
          infracost breakdown --path . \
            --format json \
            --out-file /tmp/infracost-base.json

      - name: Generate cost diff for PR branch
        run: |
          git checkout ${{ github.sha }}
          infracost diff --path . \
            --format json \
            --compare-to /tmp/infracost-base.json \
            --out-file /tmp/infracost-diff.json

      - name: Post PR comment
        run: |
          infracost comment github \
            --path /tmp/infracost-diff.json \
            --repo $GITHUB_REPOSITORY \
            --github-token ${{ secrets.GITHUB_TOKEN }} \
            --pull-request ${{ github.event.pull_request.number }} \
            --behavior update

The --behavior update flag updates the existing Infracost comment rather than creating a new one on each commit — keeps the PR clean.

Failing CI on large cost increases

You can configure Infracost to fail CI when a PR increases costs beyond a threshold. This works well as a guardrail for staging environments or cost-sensitive teams:

infracost comment github \
  --path /tmp/infracost-diff.json \
  --github-token ${{ secrets.GITHUB_TOKEN }} \
  --pull-request ${{ github.event.pull_request.number }} \
  --behavior update \
  --policy-path infracost-policy.rego
# infracost-policy.rego — fail CI if monthly cost increase > $100
package infracost

deny[msg] {
  maxDiff := 100.0
  input.diffTotalMonthlyCost > maxDiff
  msg := sprintf(
    "Monthly cost increase ($%.2f) exceeds threshold ($%.2f)",
    [input.diffTotalMonthlyCost, maxDiff]
  )
}

Cost optimization patterns

Infracost makes cost tradeoffs explicit, which is most useful when evaluating architectural decisions. Common patterns where it helps:

Right-sizing instances

Oversized instances are one of the most common sources of waste. Infracost makes the cost difference between instance types visible before deployment:

NAT Gateway costs

NAT Gateway charges both per-hour ($0.045/hour = ~$33/month) and per-GB processed. For high-throughput workloads, data processing costs dominate. Common mitigation: VPC endpoints for S3 and DynamoDB bypass the NAT Gateway entirely, eliminating data processing charges for traffic to those services.

resource "aws_vpc_endpoint" "s3" {
  vpc_id          = aws_vpc.main.id
  service_name    = "com.amazonaws.${var.region}.s3"
  route_table_ids = [aws_route_table.private.id]
}

Environment-specific sizing

Using production instance sizes in staging and dev environments is a common source of unnecessary spend. Infracost makes the cost difference concrete:

variable "instance_class" {
  default = "db.t3.micro"  # $13/month in dev
}

# Production tfvars: instance_class = "db.r6g.large"  # $240/month

Storage class selection

S3 Intelligent-Tiering automatically moves objects to cheaper storage tiers based on access frequency. For large buckets with unpredictable access patterns, the monitoring fee ($0.0025/1000 objects) is typically far less than the savings from automatic tiering. Infracost can model this with usage estimates for access frequency.

Multi-project and monorepo setups

For repos with multiple Terraform projects, Infracost can aggregate costs across all of them:

# infracost.yml — project configuration
version: 0.1

projects:
  - path: ./environments/production
    name: Production
  - path: ./environments/staging
    name: Staging
  - path: ./modules/shared-networking
    name: Shared Networking
infracost breakdown --config-file infracost.yml --format table

The output shows each project's costs separately and a combined total — useful for understanding the cost split across environments or teams.

Reading Infracost output alongside your architecture

Cost numbers are more actionable when connected to the architecture they describe. When working with Terraform that has both a Checkov security scan and an Infracost estimate, InfraSketch lets you visualize the infrastructure from the same Terraform source: paste your configuration to generate an architecture diagram, then overlay the Infracost JSON output to see per-resource cost annotations directly on the diagram. This makes it easier to communicate cost breakdowns to stakeholders who aren't reading raw JSON.

Related articles