Integrating Infracost into CI/CD: Cloud Cost Estimation for Terraform

DevOps tutorial - IT technology blog
DevOps tutorial - IT technology blog

The Problem: Cloud Cost Surprises After Deployment

Six months ago, our team shipped a routine Terraform update to boost staging database throughput. Automated tests, linting, and security scans all passed without issue. Three weeks later, finance flagged an unexpected $4,200 spike in our AWS invoice. An engineer had inadvertently provisioned multi-AZ provisioned IOPS SSD (io2) storage with 10,000 IOPS across three non-production clusters instead of standard gp3 volumes.

Most FinOps workflows rely on retrospective dashboards like AWS Cost Explorer or CloudHealth. While helpful for monthly accounting, they alert you only after the money is already spent. Triaging runaway infrastructure expenses after running terraform apply creates avoidable friction between platform teams and developers.

Shift-left practices transformed automated testing and vulnerability scanning. Bringing that same proactive mindset to cloud spending stops costly provisioning mistakes before code ever reaches your main branch.

Core Concepts: How Infracost Shifts Cost Awareness Left

Infracost parses Terraform HCL code or JSON execution plans and maps every declared resource against vendor pricing data across AWS, Azure, and Google Cloud. Because it estimates costs statically, you get rapid pricing insights without querying live cloud infrastructure.

Key Architectural Components

  • Cloud Pricing API: A managed service tracking over 3 million cloud SKUs that continuously updates whenever providers alter their hourly or tier-based rates.
  • Diff Engine: Calculates the monthly cost delta between your target branch and the incoming pull request, breaking down additions, updates, and removals.
  • Usage Profiles: Allows teams to specify dynamic usage parameters—such as 500 GB of standard S3 storage or 25 million Lambda requests—via an infracost-usage.yml file.
  • CI/CD Commenter: Publishes a markdown breakdown directly into GitHub Pull Requests or GitLab Merge Requests.

Hands-On Practice: Integrating Infracost into GitHub Actions

Let’s install the CLI locally, inspect a sample resource diff, and configure an automated GitHub Actions workflow.

Step 1: Install and Authenticate the Infracost CLI

First, install the CLI binary on your workstation to inspect cost breakdowns during local development:

# macOS installation via Homebrew
brew install infracost

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

# Authenticate to retrieve your API key
infracost auth login

Step 2: Generate a Local Cost Breakdown

Consider a simple Terraform configuration defining an EC2 instance with an attached EBS root disk:

# main.tf
provider "aws" {
  region = "us-east-1"
}

resource "aws_instance" "web_app" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "m5.xlarge"

  root_block_device {
    volume_size = 100
    volume_type = "gp3"
  }
}

Run the cost calculation directly against your directory:

infracost breakdown --path .

Infracost parses the HCL files and prints a monthly summary:

Name                                                  Monthly Qty  Unit         Monthly Cost

aws_instance.web_app
├─ Instance usage (Linux/UNIX, on-demand, m5.xlarge)          730  hours             $140.16
└─ root_block_device
   └─ Storage (general purpose SSD, gp3)                      100  GB                  $8.00

OVERALL TOTAL                                                                        $148.16

Step 3: Automate Pull Request Cost Diffs in GitHub Actions

Catching financial regressions during code review ensures spending stays transparent across engineering teams. Add your API token as a repository secret named INFRACOST_API_KEY, then add this workflow file at .github/workflows/infracost.yml:

name: Infracost Cost Estimation

on:
  pull_request:
    branches: [ main, master ]
    paths:
      - 'terraform/**'
      - '**.tf'

jobs:
  infracost:
    name: Infracost PR Cost Diff
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write

    steps:
      - name: Checkout Base Branch
        uses: actions/checkout@v4
        with:
          ref: ${{ github.event.pull_request.base.ref }}

      - name: Setup Infracost
        uses: infracost/actions/setup@v3
        with:
          api-key: ${{ secrets.INFRACOST_API_KEY }}

      # Generate baseline cost from base branch
      - name: Generate Infracost Baseline
        run: |
          infracost breakdown --path=terraform \
                              --format=json \
                              --out-file=/tmp/infracost-base.json

      - name: Checkout Current PR Branch
        uses: actions/checkout@v4

      # Generate diff against baseline
      - name: Generate Infracost Diff
        run: |
          infracost diff --path=terraform \
                         --format=json \
                         --compare-to=/tmp/infracost-base.json \
                         --out-file=/tmp/infracost-diff.json

      # Post cost breakdown as a GitHub PR comment
      - name: Post Infracost 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

Step 4: Establish FinOps Guardrails

Visibility is great, but enforcement prevents large accidental commits. You can parse the generated JSON diff with jq and break the build if a pull request increases monthly spend beyond a set limit (e.g., more than $200/month without managerial approval):

# Extract the monthly cost difference using jq
DIFF=$(jq -r '(.diffTotalMonthlyCost // 0)' /tmp/infracost-diff.json)
THRESHOLD=200

echo "Projected monthly change: $$DIFF"

if (( $(echo "$DIFF > $THRESHOLD" | bc -l) )); then
  echo "ERROR: Cost increase of $$DIFF exceeds the $$THRESHOLD limit. FinOps approval required."
  exit 1
fi

Six Months in Production: Lessons Learned

Running Infracost across 40+ microservices surfaced several practical takeaways for platform engineers:

  1. Define usage profiles early: Serverless components (AWS Lambda, DynamoDB on-demand, S3 Glacier) default to $0.00 until you supply estimated invocation and storage metrics in infracost-usage.yml.
  2. Centralize monorepo configs: If your repository maintains distinct environment paths (such as environments/staging and environments/prod), declare an infracost.yml multi-project config to evaluate all targets concurrently.
  3. Feedback accelerates self-correction: When engineers see a $280/month increase from bumping a compute tier from c5.2xlarge to c5.4xlarge inside their PR, they often right-size the workload before requesting review.

Summary

Bringing cost visibility directly into GitHub pull requests changes FinOps from a reactive monthly audit into an automated engineering safeguard. Pair Infracost with your existing CI/CD pipelines to keep cloud budgets transparent, manageable, and predictable.

Share: