Cost Guardrails Azure DevOps Extension

Know your infrastructure costs before you merge.
Cost Guardrails reviews every PR for cost impact. It works with both Terraform (plan.json) and CloudFormation (changeset.json) — auto-detected, no extra config.
On every PR, Cost Guardrails:
- Posts a cost breakdown as a PR comment (per-resource, with regions)
- Returns a decision — ALLOW, WARN, or BLOCK — that gates your merge
- Validates against your budget and guardrails
- Provides AI-powered recommendations to optimize costs
- Saves an HTML report as a build artifact
Quick Start
1. Install the Extension
An org admin installs Cost Guardrails from the Visual Studio Marketplace:
Organization Settings → Extensions → Browse Marketplace → search "Cost Guardrails" → Install
This is a one-time step. Once installed, all pipelines in the org can use task: Cost Guardrails@1.
2. Set Pipeline Variables
Go to Pipelines → Edit → Variables and add:
| Variable |
Secret |
Description |
COST_GUARDRAILS_API_KEY |
Yes |
Your Cost Guardrails API key |
COST_GUARDRAILS_BUDGET_CODE |
No |
Your budget code from Cost Guardrails dashboard (optional) |
3. Add to your pipeline
Terraform — azure-pipelines.yml:
trigger:
branches:
include: [main]
pool:
vmImage: 'ubuntu-latest'
stages:
- stage: Plan
jobs:
- job: TerraformPlan
steps:
- task: TerraformInstaller@1
inputs:
terraformVersion: '1.7.5'
# Add your AWS credentials step here
- script: |
terraform init -input=false
terraform plan -out=tfplan -input=false
terraform show -json tfplan > plan.json
displayName: 'Terraform Plan'
- publish: plan.json
artifact: plan
- stage: Cost Guardrails
dependsOn: Plan
jobs:
- job: Cost Guardrails
steps:
- download: current
artifact: plan
- task: Cost Guardrails@1
inputs:
planPath: $(Pipeline.Workspace)/plan/plan.json
budgetCode: $(COST_GUARDRAILS_BUDGET_CODE)
env:
COST_GUARDRAILS_API_KEY: $(COST_GUARDRAILS_API_KEY)
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
CloudFormation — If your pipeline already generates changeset.json, just add the Cost Guardrails stage:
- stage: Cost Guardrails
dependsOn: CreateChangeset
jobs:
- job: Cost Guardrails
steps:
- download: current
artifact: changeset
- task: Cost Guardrails@1
inputs:
planPath: $(Pipeline.Workspace)/changeset/changeset.json
budgetCode: $(COST_GUARDRAILS_BUDGET_CODE)
env:
COST_GUARDRAILS_API_KEY: $(COST_GUARDRAILS_API_KEY)
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
Generate changeset.json with: aws cloudformation describe-change-set --change-set-name <arn> > changeset.json
4. Enable branch policy
Repos → Branches → main → Branch policies:
| Setting |
Value |
| Build validation |
Select your pipeline, Automatic |
| Require reviewers |
1+ |
Without a build validation policy, the pipeline only runs after merge and Cost Guardrails can't block.
That's it. Create a PR and Cost Guardrails will post a cost review comment.
How It Works
Developer creates PR
│
├─ Build validation triggers pipeline
│
├─ Your plan job produces:
│ Terraform: terraform show -json tfplan > plan.json
│ CloudFormation: aws cloudformation describe-change-set > changeset.json
│
└─ Cost Guardrails task
│
├─ Installs cost-guardrails (pip install)
├─ Runs cost-guardrails with your inputs
├─ Posts PR comment with cost breakdown
├─ Saves result JSON + HTML report as artifacts
│
└─ Task result controls your pipeline:
Succeeded → ALLOW → PR can merge
Failed → BLOCK → PR blocked
SucceededWithIssues → WARN → PR can merge (review recommended)
Cost Guardrails posts one comment per PR and updates it on every push. No duplicate comments.
What You'll See
On every PR, Cost Guardrails posts a comment with:
- Decision badge — ALLOW (green), WARN (yellow), or BLOCK (red)
- Total monthly cost — estimated infrastructure spend
- Per-resource breakdown — cost per resource with region and pricing details
- Budget status — within budget, near limit, or over budget
- AI recommendations — optimization suggestions to reduce costs
The comment updates automatically on each push — no duplicates.
Supported IaC Types
| Type |
Input File |
How to generate |
| Terraform |
plan.json |
terraform show -json tfplan > plan.json |
| CloudFormation Changeset |
changeset.json |
aws cloudformation describe-change-set > changeset.json |
Cost Guardrails auto-detects the type from file content.
| Input |
Required |
Default |
Description |
planPath |
No |
plan.json |
Path to plan or changeset file |
budgetCode |
No |
"" |
Budget code for cost validation |
skipBudget |
No |
false |
Skip budget check (pricing-only mode) |
skipNarrative |
No |
false |
Skip AI analysis (faster runs) |
skipGuardrails |
No |
false |
Skip guardrail evaluation |
autoApprove |
No |
false |
Pass when no budget found |
postComment |
No |
true |
Post cost breakdown as PR comment |
format |
No |
markdown |
Comment format: markdown or terminal |
tag |
No |
"" |
Tag to identify this comment (e.g. production, staging). Each tag gets its own PR comment |
extraArgs |
No |
"" |
Extra CLI flags (escape hatch) |
Required environment variables
These must be mapped via env: in your pipeline:
- task: Cost Guardrails@1
inputs:
planPath: plan.json
env:
COST_GUARDRAILS_API_KEY: $(COST_GUARDRAILS_API_KEY) # secret variable
SYSTEM_ACCESSTOKEN: $(System.AccessToken) # required for PR comments
Why env: mapping? Azure DevOps doesn't auto-expose secret variables or System.AccessToken to tasks. You must explicitly pass them via the env: block.
Output Variables
| Variable |
Description |
decision |
ALLOW, WARN, or BLOCK |
monthlyCost |
Estimated monthly cost |
resultFile |
Path to the cached result JSON |
reportFile |
Path to the HTML report |
Use outputs in subsequent steps:
- task: Cost Guardrails@1
name: cost-guardrails
inputs:
planPath: plan.json
env:
COST_GUARDRAILS_API_KEY: $(COST_GUARDRAILS_API_KEY)
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
- script: |
echo "Decision: $(cost-guardrails.decision)"
echo "Monthly cost: $(cost-guardrails.monthlyCost)"
displayName: 'Check Cost Guardrails result'
Examples
Pricing only (skip budget + AI)
- task: Cost Guardrails@1
inputs:
planPath: plan.json
skipBudget: true
skipNarrative: true
env:
COST_GUARDRAILS_API_KEY: $(COST_GUARDRAILS_API_KEY)
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
Auto-approve when no budget found
- task: Cost Guardrails@1
inputs:
planPath: plan.json
budgetCode: $(COST_GUARDRAILS_BUDGET_CODE)
autoApprove: true
env:
COST_GUARDRAILS_API_KEY: $(COST_GUARDRAILS_API_KEY)
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
- stage: Cost Guardrails_Production
dependsOn: PlanProduction
jobs:
- job: Cost Guardrails
steps:
- download: current
artifact: plan-production
- task: Cost Guardrails@1
inputs:
planPath: $(Pipeline.Workspace)/plan-production/plan.json
budgetCode: $(COST_GUARDRAILS_BUDGET_CODE_PROD)
tag: production
env:
COST_GUARDRAILS_API_KEY: $(COST_GUARDRAILS_API_KEY)
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
- stage: Cost Guardrails_Staging
dependsOn: PlanStaging
jobs:
- job: Cost Guardrails
steps:
- download: current
artifact: plan-staging
- task: Cost Guardrails@1
inputs:
planPath: $(Pipeline.Workspace)/plan-staging/plan.json
budgetCode: $(COST_GUARDRAILS_BUDGET_CODE_STG)
tag: staging
env:
COST_GUARDRAILS_API_KEY: $(COST_GUARDRAILS_API_KEY)
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
Each tag posts a separate PR comment — production and staging results won't overwrite each other.
Save artifacts
- task: Cost Guardrails@1
name: cost-guardrails
inputs:
planPath: plan.json
env:
COST_GUARDRAILS_API_KEY: $(COST_GUARDRAILS_API_KEY)
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
- publish: $(cost-guardrails.resultFile)
artifact: cost-guardrails-result
condition: always()
- publish: $(cost-guardrails.reportFile)
artifact: cost-guardrails-report
condition: always()
Build Service Permissions
For PR comments to work, the pipeline's build service identity needs permission:
Project Settings → Repos → Security → {Project} Build Service ({Org}):
| Permission |
Value |
| Contribute to pull requests |
Allow |
Without this, Cost Guardrails runs but can't post PR comments.
Troubleshooting
| Problem |
Cause |
Fix |
| PR comment not posted |
Missing SYSTEM_ACCESSTOKEN |
Add env: SYSTEM_ACCESSTOKEN: $(System.AccessToken) |
| PR comment 403 error |
Build service lacks permission |
Project Settings → Repos → Security → "Contribute to pull requests" = Allow |
| BLOCK doesn't prevent merge |
No build validation policy |
Repos → Branches → main → Build validation → add your pipeline |
COST_GUARDRAILS_API_KEY not found |
Variable not set or not secret |
Pipelines → Edit → Variables → add as secret |
| Task not found |
Extension not installed |
Org admin: Organization Settings → Extensions → install Cost Guardrails |
| API timeout on first run |
Cold start |
Re-run pipeline — subsequent runs complete in ~5 seconds |
Shift-left cost governance for cloud infrastructure