terraformawscost-estimationci-cd

How to estimate AWS costs from Terraform before deploying

From a single CLI command to PR comments in CI that block over-budget merges, this is the practical guide to Terraform cost estimation. No AWS credentials, no terraform plan, no SaaS account.

The C3X Team··11 min read

Quick answer

Install C3X (brew install c3xdev/tap/c3x), point it at your Terraform directory (c3x estimate --path .), and you get a line-item monthly cost in seconds. No AWS credentials, no terraform plan, no SaaS account. For PR comments in CI, add a two-line GitHub Action. For usage-based resources like Lambda and S3, define expected volumes in c3x-usage.yml.

The decision to provision an expensive AWS resource almost always happens in a Terraform PR, not in a budget meeting. By the time the monthly bill arrives, the m6i.8xlarge change is in production and rolling it back is paperwork. This is the gap a Terraform cost estimator fills: showing the dollar impact of a code change before anyone hits apply.

This post walks through how to estimate AWS costs from Terraform end-to-end, from a single command on your laptop to PR comments in CI that block merges over budget.

Step 1: Install the CLI

C3X is Apache 2.0 open source. There's no API key, no signup, no SaaS account.

# macOS or Linux via Homebrew
brew install c3xdev/tap/c3x

# Linux/macOS via curl
curl -fsSL https://c3x.dev/install.sh | sh

# Docker
docker pull c3xdev/c3x:latest

Verify it works:

c3x --version

Step 2: Estimate a single project

Point C3X at your Terraform directory. It parses the HCL, looks up pricing for each resource, and outputs a per-resource cost breakdown.

c3x estimate --path /path/to/terraform

Example output for a small web stack:

 Name                                                Monthly Qty  Unit         Monthly Cost

 aws_db_instance.postgres
 ├─ Database instance (on-demand, Multi-AZ, db.r5.large)     730  hours          $365.00
 └─ Storage (general purpose SSD, gp3)                       200  GB              $46.00

 aws_instance.api
 ├─ Instance usage (Linux/UNIX, on-demand, m5.xlarge)        730  hours          $140.16
 ├─ root_block_device
 │  └─ Storage (general purpose SSD, gp2)                    100  GB              $10.00
 └─ ebs_block_device[0]
    └─ Storage (general purpose SSD, gp3)                    500  GB              $40.00

 aws_nat_gateway.main
 ├─ NAT gateway                                              730  hours           $32.85
 └─ Data processed                                Monthly cost depends on usage: $0.045 per GB

 OVERALL TOTAL                                                                   $633.01

Notice three things in the output. First, every resource is shown with its sub-components (a single aws_instance can have an EBS root volume, additional volumes, and instance hours, each priced separately). Second, usage-based dimensions like NAT data processing are flagged explicitly. Third, the total is monthly, based on 730 hours per month for continuous resources.

Step 3: Add a usage file for usage-based resources

Some AWS resources don't have a deterministic monthly cost from Terraform alone. Lambda functions bill per invocation. S3 bills per GB stored and per request. NAT Gateway bills per GB processed. To estimate these accurately, define expected volumes in c3x-usage.yml.

# c3x-usage.yml
resource_usage:
  aws_lambda_function.api:
    monthly_requests: 10000000
    request_duration_ms: 200

  aws_s3_bucket.logs:
    standard_storage_gb: 500
    monthly_tier_1_requests: 1000000
    monthly_tier_2_requests: 5000000

  aws_nat_gateway.main:
    monthly_data_processed_gb: 2000

Then pass the file to estimate:

c3x estimate --path . --usage-file c3x-usage.yml

For more on the usage file format, see the usage file documentation. The file structure is intentionally minimal; C3X infers reasonable defaults for fields you don't specify.

Step 4: Diff two branches

The killer use case is showing the cost delta of a Terraform PR. Before merging, you want to see: this PR adds $210/month, dominated by the instance type change from m5.xlarge to m6i.2xlarge.

c3x diff --path . --compare-to main

Example output:

 ~ aws_instance.api
   ├─ Instance usage (Linux/UNIX, on-demand, m5.xlarge -> m6i.2xlarge)  +$170.24 ($140.16 -> $310.40)
   └─ ebs_block_device[0]
      └─ Storage (general purpose SSD, gp3, 500 -> 1000 GB)             +$40.00 ($40.00 -> $80.00)

 Monthly cost change: +$210.24 ($633.01 -> $843.25)

The diff output is what teams put in PR comments. It compresses the full estimate into "what changed, by how much, and total."

Step 5: Add to CI with two lines of YAML

The full workflow is a single composite action that installs C3X, runs the diff, and posts the comment on the PR.

# .github/workflows/cost.yml
on: pull_request

jobs:
  cost:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: c3xdev/setup-c3x@v1
        with:
          path: .

That's the entire workflow. The action installs the C3X binary, runs the diff against the base branch, and posts a comment on the PR. No secrets to configure, no API keys.

For details on GitLab CI, Bitbucket Pipelines, Atlantis, and Spacelift integration, see the CI/CD integration documentation.

Step 6: Add a budget guardrail

If you want the CI step to fail when costs cross a threshold, pass --budget to estimate or diff:

# Fail if total monthly cost exceeds $1,000
c3x estimate --path . --budget 1000

# Fail if a PR adds more than 20% to the cost
c3x diff --path . --compare-to main --budget-increase 20

Exit code 1 if the budget is exceeded. CI fails, the PR is blocked until either the cost is reduced or a manual override is given.

What this looks like in practice

Three patterns commonly emerge once teams add cost estimates to PRs:

  • Smaller PRs. Engineers see the dollar impact and break large changes into smaller pieces. "This single PR adds $400/month" becomes "I'll split out the new feature from the instance resize."
  • Better choices on instance sizing. The cost diff surfaces over-provisioning. m6i.2xlarge for a workload that fits in m6i.large is a $200/month decision that gets walked back.
  • Caught NAT Gateway blowups. Adding a new VPC with three NAT Gateways adds $100/month base before any traffic. Cost diff makes this obvious. See our NAT Gateway alternatives post for what to do instead.

Beyond estimates: optimization recommendations

C3X also has a recommend command that suggests concrete changes:

c3x recommend --path .

Sample output:

3 recommendation(s) found:

  1. Upgrade to newer instance generation (m5.xlarge -> m7i.xlarge)
     Resource: aws_instance.web (aws_instance)
     The m7i family has better price-performance than m5.

  2. Switch EBS volume from gp2 to gp3
     Resource: aws_ebs_volume.data (aws_ebs_volume)
     gp3 volumes are up to 20% cheaper with better baseline performance.

  3. Consider VPC endpoints to reduce NAT Gateway costs
     Resource: aws_nat_gateway.main (aws_nat_gateway)
     NAT Gateway charges $0.045/GB. VPC endpoints can eliminate these charges.

These are mechanical optimizations. They don't require a code review or product judgment; they're cost wins with no functional downside. Worth running periodically on existing Terraform, not just new changes.

FAQ

Can I estimate AWS costs from Terraform without running terraform plan?

Yes. C3X parses Terraform HCL directly and uses public AWS pricing data to compute monthly costs. There's no terraform plan invocation, no AWS credentials required, and no apply needed. The estimate runs in seconds against the source code, not against your AWS account.

How accurate are Terraform cost estimates?

Resource-level costs (EC2, RDS, EBS, etc.) are accurate to the cent because they use the same AWS Pricing API that backs the billing system. Usage-based resources (Lambda invocations, S3 storage, data transfer) require a usage file with expected volumes to estimate accurately. Without a usage file, those resources show '$0 with usage required' notes.

Does C3X work with Terragrunt?

Yes. C3X reads Terragrunt configurations directly: terragrunt.hcl files, included modules, generated providers, and remote state references all work. You point c3x estimate at the Terragrunt directory and it walks the include chain to find the right module.

What about reserved instances and savings plans?

Define purchaseOption: 'reserved' or 'savings_plan' on the resource in c3x-usage.yml. C3X applies the discounted rate from the AWS Pricing API. Reserved Instance term length (1-year or 3-year) and offering class (no-upfront, partial, all-upfront) are configurable.

How does C3X handle usage-based resources like Lambda and S3?

Usage-based resources need expected volumes specified in a c3x-usage.yml file. For Lambda, that's monthly invocations and average duration. For S3, that's storage size by tier, request counts, and egress volume. Without the usage file, c3x estimate reports per-unit rates and flags the resources as usage-dependent.

Does C3X support CloudFormation?

Yes. C3X parses both YAML and JSON CloudFormation templates with the same engine that handles Terraform. Same per-resource estimation, same usage file, same output format.

Where to go from here

The fastest path to a working setup:

  1. Install the CLI and run c3x estimate --path . on any Terraform project. See the quickstart for full instructions.
  2. Add the GitHub Action to your repo. Cost diffs in PR comments within one merge. See the CI/CD docs.
  3. Browse the resource catalog for per-resource pricing details on every supported Terraform resource.

If you're comparing tools, our honest comparison with Infracost covers where each one wins.

Try C3X on your own Terraform

Free and open source. No API key required. One command to install, one command to estimate.