Skip to content

Cost Governance Guide

Budget alerts, forecast notifications, and anomaly detection for every deployment.

Every IaC deployment in this project must include cost monitoring resources. This is enforced by the iac-cost-repeatability instruction, which auto-applies to all .bicep, .tf, and implementation plan files.

The rule is simple: no budget, no merge. Challenger reviews verify cost monitoring exists, and CI validators flag missing budget resources.

Every deployment must include an Azure Budget resource with three forecast-based alert thresholds:

ThresholdTypeAction
80%ForecastEmail notification to owner parameter
100%ForecastEmail notification + action group
120%ForecastEmail notification + action group
@description('Monthly budget amount in USD')
param budgetAmount int
@description('Technical contact email for alerts')
param technicalContact string
resource budget 'Microsoft.Consumption/budgets@2023-11-01' = {
name: 'budget-${projectName}-${environment}'
properties: {
timePeriod: {
startDate: '2026-01-01'
}
timeGrain: 'Monthly'
amount: budgetAmount
category: 'Cost'
notifications: {
forecast80: {
enabled: true
operator: 'GreaterThanOrEqualTo'
threshold: 80
thresholdType: 'Forecasted'
contactEmails: [technicalContact]
}
forecast100: {
enabled: true
operator: 'GreaterThanOrEqualTo'
threshold: 100
thresholdType: 'Forecasted'
contactEmails: [technicalContact]
}
forecast120: {
enabled: true
operator: 'GreaterThanOrEqualTo'
threshold: 120
thresholdType: 'Forecasted'
contactEmails: [technicalContact]
}
}
}
}
variable "budget_amount" {
description = "Monthly budget amount in USD"
type = number
}
variable "technical_contact" {
description = "Technical contact email for alerts"
type = string
}
resource "azurerm_consumption_budget_resource_group" "this" {
name = "budget-${var.project_name}-${var.environment}"
resource_group_id = azurerm_resource_group.this.id
amount = var.budget_amount
time_grain = "Monthly"
time_period {
start_date = "2026-01-01T00:00:00Z"
}
notification {
operator = "GreaterThanOrEqualTo"
threshold = 80
threshold_type = "Forecasted"
contact_emails = [var.technical_contact]
}
notification {
operator = "GreaterThanOrEqualTo"
threshold = 100
threshold_type = "Forecasted"
contact_emails = [var.technical_contact]
}
notification {
operator = "GreaterThanOrEqualTo"
threshold = 120
threshold_type = "Forecasted"
contact_emails = [var.technical_contact]
}
}
Alert TypeTriggers WhenUse Case
ForecastProjected spend will exceed threshold by month-endEarly warning — time to act
ActualSpend has already exceeded thresholdReactive — damage already done

This project uses forecast alerts exclusively because they provide advance warning. By the time an actual-spend alert triggers, the budget is already blown.

In addition to budget alerts, enable Azure Cost Management anomaly alerts to catch unexpected spend spikes:

  • Configure via Azure Cost Management in the portal
  • Alert on spend patterns that deviate from historical baselines
  • Notify the technicalContact parameter

Use parameterised budgets that scale by environment:

EnvironmentTypical BudgetRationale
devLowMinimal resources, short-lived
stagingMediumProduction-like but limited use
prodFullProduction workload capacity

Set the budget amount via .bicepparam or terraform.tfvars — never hardcode it in the template.

The cost-estimate-subagent uses the Azure Pricing MCP server to query real-time SKU pricing during architecture review (Step 2) and as-built documentation (Step 7). Key tools:

ToolPurpose
azure_cost_estimateEstimate costs based on usage patterns
azure_bulk_estimateMulti-resource estimate in one call
azure_price_compareCompare prices across regions and SKUs
azure_ri_pricingReserved Instance pricing and savings
azure_region_recommendFind cheapest regions for a service

The Microsoft Learn MCP server provides microsoft_docs_search() for looking up service-specific pricing documentation.

The cost governance instruction enforces zero hardcoded values:

  • projectName must be a parameter with no default
  • All tag values must reference parameters
  • Budget amounts must be parameterised
  • .bicepparam / terraform.tfvars is the only place for project defaults

The Challenger reviews verify two mandatory cost categories:

Cost Monitoring:

  • Budget resource exists
  • Forecast alerts at 80%, 100%, 120% thresholds
  • Anomaly detection configured
  • technicalContact parameter for notifications

Repeatability:

  • No hardcoded project names or values
  • projectName is a required parameter
  • Template deploys to any tenant/region/subscription

After deployment, verify budget alerts are active:

Terminal window
# List budgets in the resource group
az consumption budget list \
--resource-group rg-${PROJECT}-${ENV}
# Check budget notifications
az consumption budget show \
--budget-name budget-${PROJECT}-${ENV} \
--resource-group rg-${PROJECT}-${ENV}