Budget guardrails in CI: blocking PRs that exceed cost limits
Cost regressions are a category of bug that production-quality teams catch automatically. Here's how to add a budget gate to GitHub Actions, GitLab CI, Bitbucket, Atlantis, and Spacelift in five minutes.
Quick answer
Add c3x diff --path . --compare-to main --budget-increase 20 (or --budget 5000 for absolute limits) to your CI pipeline. The command exits non-zero if cost crosses the threshold, failing the job. Combined with GitHub branch protection requiring the job to pass, PRs cannot merge over budget. Same flags work for GitLab CI, Bitbucket Pipelines, Atlantis, and Spacelift.
Cost regression is a category of bug that production-quality engineering teams catch automatically. Type errors get caught by the type checker, broken builds by CI, regressions by tests. Cost regressions get caught by an unhappy CFO three weeks later, after the PR is in production and rollback is expensive.
Adding a cost gate to CI fixes this. The implementation is one flag on the c3x diff command and one branch protection rule. This post walks through both patterns end-to-end with the exact YAML for the major CI systems.
The two patterns: absolute budget vs increase budget
C3X supports two ways to gate on cost.
Absolute budget
Sets a hard ceiling on monthly cost. Right when you have a fixed budget commitment from finance and any PR that pushes you above is blocked regardless of size.
c3x estimate --path . --budget 5000Exits with code 1 if the estimated monthly cost exceeds $5,000. Run this on PRs and the job fails when over budget.
Increase budget
Sets a percentage cap on cost growth between branches. Right for large existing infrastructure where any single PR adding 5-10% usually represents either over-provisioning or a misconfiguration.
c3x diff --path . --compare-to main --budget-increase 20Exits with code 1 if the PR increases monthly cost by more than 20% over the base branch.
When to use which
For most teams, start with --budget-increase. It catches PR-level regressions without requiring you to maintain an accurate absolute budget number that gets stale. Add --budget once you have a real finance commitment to defend.
Both can be combined. Pass --budget AND --budget-increase to your CI script; the job fails if either threshold is crossed.
GitHub Actions setup
The minimum viable cost gate in GitHub Actions is the setup action plus the diff command:
# .github/workflows/cost-gate.yml
name: Cost Gate
on: pull_request
jobs:
cost-gate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: c3xdev/setup-c3x@v1
with:
path: .
- name: Check cost budget
run: |
c3x diff \
--path . \
--compare-to ${{ github.base_ref }} \
--budget-increase 20Two things to note. First, fetch-depth: 0 is required because c3x diff needs the base branch's Terraform to compare against, and the default shallow checkout doesn't include it. Second, the c3xdev/setup-c3x action posts the PR comment regardless of whether the budget passes, so reviewers see the diff even when CI fails.
For the full setup including usage file integration and self-hosted pricing API, see the CI/CD integration docs.
Branch protection rule
For the cost gate to actually block merges, configure branch protection in the repo settings:
- Repo Settings → Branches → Add rule for main
- Require status checks to pass before merging
- Select "cost-gate" as a required check
- Require branches to be up to date before merging
After this, no PR can merge to main with a failing cost gate. Engineers either fix the cost issue or get a manual override from a repo admin.
GitLab CI setup
Same pattern in GitLab CI:
# .gitlab-ci.yml
stages:
- cost-gate
cost-gate:
stage: cost-gate
image: c3xdev/c3x:latest
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
script:
- c3x diff --path . --compare-to $CI_MERGE_REQUEST_TARGET_BRANCH_NAME --budget-increase 20
- c3x comment gitlab --path . --merge-request $CI_MERGE_REQUEST_IIDSet the GITLAB_TOKEN environment variable (a project access token with comment permission) so the comment step works. Mark this job as a required-to-merge check in the MR approval rules.
Bitbucket Pipelines setup
# bitbucket-pipelines.yml
pipelines:
pull-requests:
'**':
- step:
name: Cost Gate
image: c3xdev/c3x:latest
script:
- c3x diff --path . --compare-to $BITBUCKET_PR_DESTINATION_BRANCH --budget-increase 20
- c3x comment bitbucket --path . --repo $BITBUCKET_REPO_FULL_NAME --pr-id $BITBUCKET_PR_IDMark the step as required in branch restrictions. Bitbucket then prevents merge while the gate fails.
Atlantis and Spacelift
Both Atlantis and Spacelift support pre-apply commands that can run c3x. Configure them to invoke c3x diff with the budget flag before terraform apply.
Atlantis example in atlantis.yaml:
# atlantis.yaml
version: 3
projects:
- dir: .
workflow: c3x-gated
workflows:
c3x-gated:
plan:
steps:
- init
- run: c3x diff --path . --compare-to main --budget-increase 20
- planIf c3x exits non-zero, Atlantis stops and reports the failure to the PR. terraform plan never runs.
Handling the override case
Sometimes a cost increase is intentional and approved. A new product launch needs more capacity. A migration adds parallel infrastructure during the cutover. The budget gate has to support these cases without becoming a permanent CI failure that erodes team trust in the check.
Three patterns work:
Label-based override
Add a 'cost-override' label to the PR. CI checks for the label and skips the budget check when present. This is the simplest pattern:
- name: Check cost budget
if: >
!contains(github.event.pull_request.labels.*.name, 'cost-override')
run: c3x diff --path . --compare-to ${{ github.base_ref }} --budget-increase 20Combine with a CODEOWNERS rule requiring a finance or platform team member to apply the label to prevent abuse.
Higher absolute budget for production-cutover branches
Use --budget with different values per target branch. Production gets a strict $5,000 cap; release-prep branches get $7,500 for the cutover period; main gets back to $5,000 after the merge.
Bypass on commit message
Less secure but useful for emergencies. Check for a specific marker in the commit message and skip the budget check. This depends on commit policy enforcement to prevent abuse, so it's less robust than label-based.
What to actually set as the threshold
Two opinions on initial values:
For new repos with no existing infrastructure
Start with a generous absolute budget that gives the team headroom for the next 6 months of growth, set --budget-increase to 50% to catch only the most egregious PRs, then tighten over time as you understand the baseline.
For existing repos with established infrastructure
Start with --budget-increase 15-20. This catches single-PR regressions without too many false positives. Run the gate in warning-only mode (script: ... || true) for the first month to gather data, then make it blocking.
Don't start at --budget-increase 5%. Genuine refactors and growth often legitimately push past 5%. False positives erode trust in the check, leading engineers to override it routinely and the check stops mattering.
Beyond the budget gate
The cost gate is the first line of defense. Three complementary practices make the check more valuable:
Surface optimization opportunities in the comment
Add c3x recommend to the same CI workflow:
- run: c3x recommend --path . >> $GITHUB_STEP_SUMMARYCost recommendations (m5 → m7i, gp2 → gp3, NAT alternatives) appear in the PR summary. Engineers see the changes they could make before the cost gate fails. See our instance family comparison and gp2-to-gp3 migration post for the most common recommendations.
Weekly cost report on the main branch
Add a scheduled workflow that runs c3x estimate on main weekly and posts the result to Slack or your team's preferred channel. Trends in the total cost number surface drift even when individual PRs pass the gate.
Per-resource alerts
For high-cost resource types (NAT Gateway, RDS instances, large EC2), add per-resource thresholds in c3x-config.yml. The PR comment will flag any resource adding more than $X/month even if the total budget passes.
FAQ
How do I block a PR if its Terraform cost exceeds a threshold?
Add c3x diff or c3x estimate to your CI pipeline with the --budget or --budget-increase flag. If the cost crosses the threshold, the command exits with code 1 and the CI job fails. Combine with GitHub branch protection requiring the job to pass, and the PR cannot merge until either cost is reduced or someone manually overrides.
Should I gate on absolute cost or cost increase?
Both have their place. Absolute budget (--budget 5000) is right for projects with a fixed monthly cost target. Cost-increase gating (--budget-increase 20) is right for protecting against PR-level regressions on already-large infrastructure where a 20% jump matters more than the absolute number.
What about emergency PRs that legitimately need to add cost?
Use GitHub's required-check override or a label-based bypass. Mark the PR with a 'cost-override' label and configure your CI to skip the budget check when the label is present. Make the override require an approving review from a finance-empowered reviewer to prevent abuse.
Does the budget check work with usage-based resources?
Yes if you supply a c3x-usage.yml file. Without usage data, resources like Lambda and S3 show as $0 in the estimate, which means the budget check passes even though real production cost may be high. Always supply expected usage for cost-bearing usage-based resources in the same PR that introduces them.
Can I have different budgets per environment?
Yes. Use --config-file to point at different c3x-config.yml files per environment (production, staging, dev) or pass different --budget values from the workflow based on the target branch. Production typically has the strictest budget; dev/staging are usually higher to allow experimentation.
What's the difference between --budget and --budget-increase?
--budget sets an absolute monthly ceiling: 'fail if total cost exceeds $5000'. --budget-increase sets a relative ceiling vs the base branch: 'fail if this PR adds more than 20% to current cost'. Use --budget for absolute control. Use --budget-increase to catch unusual spikes without re-evaluating the absolute number every quarter.
Getting started
The fastest path from zero to a working budget gate:
- Install C3X locally and run
c3x estimate --path .on the current main branch. Note the monthly total. See the quickstart. - Add the GitHub Action (or equivalent for your CI) without any --budget flag. Verify it posts cost diffs on PRs.
- Add
--budget-increase 25as a warning. Watch how often it fires for a week. - Adjust to the level that catches genuine regressions but doesn't fire on routine work. Make it blocking via branch protection.
For full integration details across all supported CI systems, see the CI/CD documentation. For the broader workflow including PR comments and recommendations, see how to estimate AWS costs from Terraform.
Share this post
Try C3X on your own Terraform
Free and open source. No API key required. One command to install, one command to estimate.