The first surprise Azure bill is a rite of passage: a test VM left running over a long weekend, a forgotten public IP, a premium disk — and the invoice lands at three times what you expected. The cruel part is that Azure knew the spend was climbing the whole time; it never told you, because nobody told it to. That is what an Azure Cost Management budget is for — a spending threshold you set on a scope (a subscription, resource group, or management group) that watches accumulated cost and emails you, pings an Action Group, or kicks off automation the moment spend crosses a line you drew.
A budget is not a spending cap — the single most important thing to grasp, and the thing most beginners get wrong. Creating one does not stop resources or block deployments when you blow past the number; it is an alerting control, not an enforcement one. At 80% Azure sends an alert; it powers nothing off. Enforcement, if you want it, you wire up yourself by pointing the alert at an Action Group that runs a Logic App, Function, or Automation runbook. We build exactly that pipeline here, so you know precisely where the line between “Azure tells you” and “Azure does something” sits.
By the end you will have created a real budget end to end — in the portal, with the az CLI, and as reusable Bicep — wired its threshold alerts to a plain email and a multi-channel Action Group, validated it, and torn it all down. You will also know the gotchas that make alerts silently fail to arrive, and how to right-size thresholds so the alert fires early enough to matter but not so often you ignore it.
What problem this solves
Cloud spend is consumption-based and invisible by default. Unlike a fixed monthly server bill, your Azure cost is the running sum of thousands of tiny metered events — VM seconds, GB stored, GB egressed, requests served — accruing silently until the invoice at month end. There is no built-in “you’ve spent enough, stop” line, so most people learn their spend tripled only when finance forwards the bill days into the next month, far too late to act on the cause.
What breaks without this: a developer spins up a Standard_D8s_v5 to test something, forgets it, and it bleeds roughly ₹35,000–40,000 a month doing nothing; an accidental premium storage account leaks cost slowly enough that no single day looks alarming; a subscription handed to a new team quietly outspends its quarterly allocation. In every case the spend was visible in Cost Analysis the whole time — but nobody was watching.
Who hits this: everyone with an Azure subscription, but most painfully learners and small teams on a personal or pay-as-you-go subscription (no FinOps team watching) and platform teams handing subscriptions to other groups (the bill’s owner is not the person creating resources). A budget is the cheapest, fastest fix — free, ten minutes — turning “we found out at month end” into “we got an email at 80%.” It is the natural next control after Free Account, Credits and Spending Caps and starting on Cost Analysis in your first 30 days.
Learning objectives
By the end of this article you can:
- Explain what an Azure budget is and is not — an alerting control on accumulated cost, not a hard spending cap — and where enforcement actually comes from.
- Choose the right scope (subscription, resource group, or management group) and reset period for a budget, and pick between actual and forecasted alert conditions.
- Create a budget with multiple threshold alerts (e.g. 50/80/100/forecast-110) wired to email recipients, in both the portal and the
azCLI. - Build an Action Group and attach it to a budget so a threshold breach fans out to email, SMS, a webhook, a Logic App, or an Automation runbook.
- Author a reusable Bicep module that deploys the budget and its notifications as code, so every subscription gets the same guardrail.
- Validate that a budget’s alert configuration is correct, diagnose why alert emails fail to arrive, and tear down everything cleanly.
- Right-size thresholds and reset periods so alerts fire early enough to act on but rarely enough that you keep paying attention.
Prerequisites & where this fits
You need an Azure subscription you can create resources in, and the right role on the scope you target. For budgets the practical minimum is Cost Management Contributor at that scope, or a broader role like Contributor or Owner; to merely read cost data you need at least Cost Management Reader. Billing-account or management-group budgets need a billing role (Billing account Contributor) or management-group access — more in the scope section. Be comfortable opening Cloud Shell (or a local az logged in via az login), reading JSON, and have glanced at Cost Analysis once so the numbers a budget watches are familiar.
This sits at the base of the cost-governance stack. A budget is the detective control — it tells you spend crossed a line. Above it sit preventive controls: Azure Policy effects like Deny and Audit that stop expensive SKUs being created, and the Azure Resource Hierarchy that decides where you place budgets. For learners the upstream control is the hard spending cap on a Free/MSDN subscription; for organisations the downstream discipline is FinOps at scale. This guide is the do-it-now middle.
Where a budget sits among the controls people confuse it with:
| Control | Type | What it does | Stops spend? |
|---|---|---|---|
| Budget | Detective / alert | Watches accumulated cost, alerts at thresholds | No — alerts only |
| Spending cap (Free/MSDN only) | Preventive | Disables resources when credit is exhausted | Yes — but only on credit-based subs |
| Azure Policy (Deny) | Preventive | Blocks creation of disallowed/expensive SKUs | Yes — at deploy time |
| Action Group + automation | Reactive | Runs a Logic App/Function/runbook on an alert | Only what you code it to do |
| Cost Analysis | Visibility | Shows where money went, after the fact | No |
Core concepts
Five moving parts make every later step obvious. Learn these and the portal blades stop being mysterious.
A budget is a threshold on accumulated cost over a period. You define an amount (e.g. ₹20,000), a reset period (monthly, quarterly, or annually), and a scope (subscription, resource group, management group, or billing account). Azure sums the cost charged to that scope in the current period; when the running total crosses an alert threshold, the notification fires. Each new period the total resets to zero.
It alerts, it does not enforce. Hitting 100% does not stop, throttle, deallocate, or block anything — resources run, deployments succeed, the meter ticks. The budget’s only action is to notify. If you want a breach to do something, you build that downstream of the alert; there is no toggle that turns a budget into a cap.
Thresholds come in two flavours. An actual threshold fires when real, accumulated spend crosses a percentage (“80% actual”). A forecasted threshold fires when Azure’s projection of period-end spend crosses a percentage, which can fire on day 10 if you are spending fast. Forecast is the early warning; actual is the record. Use both — one budget carries up to five thresholds, so you typically set 50/80/100 actual plus a 100-forecast.
Scope decides what it watches and who can create it. A subscription budget sums every charge in that subscription; a resource group budget watches one RG (ideal per team/environment); a management group or billing account budget rolls up many subscriptions and needs a billing/management-group role. Pick the scope that matches the unit of accountability.
Notifications fan out through email or an Action Group. The simplest is a list of email addresses. The powerful one is an Action Group — a reusable bundle of receivers (email, SMS, push, voice, webhook, Logic App, Function, runbook, ITSM/Event Hub) triggered on breach. Attaching one is the bridge from a plain email to automation, chat-ops, and incident tooling.
The vocabulary side by side, so portal labels line up with the concepts:
| Term | One-line definition | Where you set it | Why it matters |
|---|---|---|---|
| Budget amount | The spend figure thresholds are a % of | Budget creation | The line everything else measures against |
| Reset period | Monthly / Quarterly / Annually rollover | Budget creation | Resets accumulated cost to zero |
| Scope | Sub / RG / management group / billing | Budget creation | What charges are summed; who can create it |
| Threshold (%) | Percentage of amount that triggers an alert | Alert conditions | When you get told |
| Actual vs Forecasted | Real spend vs projected end-of-period spend | Per threshold | Early warning vs after-the-fact |
| Notification / alert | The email or Action Group fired on breach | Alert conditions | How you get told |
| Action Group | Reusable bundle of alert receivers + actions | Separate resource | Bridge from alert to automation |
| Cost vs Usage budget | Budget on money vs on a quantity (e.g. hours) | Budget type | Most people want a Cost budget |
Choosing scope, period and amount
Three decisions define a budget before you touch an alert: where it watches (scope), how often it resets (period), and how much the line is (amount). Get these right and the alerts are easy; wrong, and the budget either never fires or fires constantly.
Scope — the unit of accountability
Scope is the most consequential choice: it decides both what cost is summed and what role you need to create the budget.
| Scope | Watches | Role needed to create | Best for |
|---|---|---|---|
| Subscription | All charges in one subscription | Cost Management Contributor (or Contributor/Owner) on the sub | The default; one app or team per sub |
| Resource group | Charges for one RG only | Cost Management Contributor on the RG | Per-environment / per-team guardrails |
| Management group | Rolled-up charges of all child subs | Management group access + cost role | Department / business-unit roll-ups |
| Billing account / profile | Charges across the billing scope | Billing account Contributor (EA/MCA role) | Finance-owned, whole-account ceiling |
Practical rule: put a subscription-level budget on every subscription as the backstop, add resource-group budgets where one subscription hosts multiple teams/environments, and reserve management-group budgets for finance/platform owners holding the billing role. For a learner with one subscription, a single subscription-scope budget is exactly right.
Reset period — match it to how you are billed
The reset period controls when accumulated cost rolls back to zero. Azure budgets support Monthly, Quarterly, and Annually.
| Reset period | Resets cost to zero | Use when | Watch-out |
|---|---|---|---|
| Monthly | First of each month | You are billed monthly / act monthly (most common) | Early-month spikes can look alarming before forecast settles |
| Quarterly | Start of each quarter | Budgets are allocated per quarter | A bad first month may not trigger until late |
| Annually | Budget’s start anniversary | Annual project / commitment tracking | Coarse; pair with monthly for early warning |
Most subscriptions bill monthly, so a Monthly budget aligned to the calendar month is the right default. Set a start date (first of a month for a monthly budget) and leave the end date far out (Azure defaults it years ahead) so the budget keeps running.
Amount — set it where an alert is meaningful
The amount is the figure your thresholds are percentages of. Set it from real data, not a guess: open Cost Analysis, read your typical monthly spend, and set the budget a sensible margin above it so the 80% alert lands at “noticeably higher than normal,” not “every month.” If steady spend is ₹15,000/month, a ₹20,000 budget puts 80% at ₹16,000 (a believable early signal) and 100% at the figure you do not want to cross. Set it at normal spend and it screams monthly until you ignore it; set it 10× too high and it never fires. The number should hurt a little to cross.
Designing the threshold alerts
A budget with no alerts does nothing useful — it just draws a line you cannot see. The alerts are the product. Each is a (type, threshold %, recipients) triple, and one budget holds several.
Actual vs forecasted, and the percentages to pick
Use both types: forecasted alerts warn you before you overspend; actual alerts record that you did. A solid default ladder for a monthly budget:
| Alert | Type | Threshold | What it tells you | Who should get it |
|---|---|---|---|---|
| 1 | Actual | 50% | “Halfway through the budget” — informational | Owner (email) |
| 2 | Actual | 80% | “Getting close” — time to look | Owner + team (email / Action Group) |
| 3 | Actual | 100% | “Budget reached” — act now | Owner + team + automation (Action Group) |
| 4 | Forecasted | 100% | “On track to blow the budget by month end” — early warning | Owner + team (Action Group) |
The forecasted-100% alert is the one that saves you: it can fire on day 8 if a runaway resource pushes the projection over the line, giving you three weeks to react instead of a post-mortem. Azure recalculates the forecast as data arrives — noisiest early, tightening as the month progresses.
Thresholds, limits and the gotchas
The mechanics that trip people up, with the real constraints:
| Setting / behaviour | Detail / limit | Gotcha |
|---|---|---|
| Threshold value | An integer percentage of the amount, 0–1000 | You can alert above 100% (e.g. 110%) to catch genuine overruns |
| Alerts per budget | Up to 5 notification thresholds | Plan your ladder; you cannot add a 6th |
| Email recipients per notification | Up to 50 email addresses | Use a distribution list, not 50 individuals |
| Evaluation cadence | Azure evaluates cost data on a recurring cycle (roughly every 8–24 hours); it is not real-time | A breach is not detected the instant it happens — there is lag |
| Cost data latency | Usage records can be hours to a day behind real consumption | Budgets watch charged cost; very recent spend may not be summed yet |
| One alert per threshold per period | A given threshold fires once per reset period when first crossed | You will not get re-spammed every cycle for the same threshold |
contactRoles / contactGroups |
Notifications can target role (Owner/Contributor/Reader) and Action Groups, not just emails | Powerful for ops; covered below |
The two that cause the most “budgets are broken” tickets: budgets are not real-time (an evaluation cadence plus a data-latency tail — no alert the second you deploy), and budgets alert but never enforce (people set 100% expecting a stop, get only an email). Both are by design.
Wiring alerts to Action Groups and automation
Email is fine for a person. To reach a team on multiple channels, or make a breach do something, you attach an Action Group — a standalone, reusable resource bundling receivers that any alert (budget, metric, log) can fire. Build it once, reuse it everywhere.
What an Action Group can fan out to
| Receiver type | What it does | Typical use for a cost alert |
|---|---|---|
| Sends mail to one or more addresses | Notify the cost owner / DL | |
| SMS | Texts a phone number | Page the on-call for a hard overrun |
| Azure app push | Push to the Azure mobile app | Quick personal heads-up |
| Voice | Phone call | Severe-overrun escalation |
| Webhook | POSTs the alert JSON to a URL | Post to Teams/Slack via an incoming webhook |
| Logic App | Triggers a Logic App workflow | Orchestrate: notify + tag + open a ticket |
| Azure Function | Invokes a function | Custom logic — e.g. deallocate dev VMs |
| Automation runbook | Runs a PowerShell/Python runbook | Stop/start resources, scale down |
| ITSM / Event Hub | Pushes to ServiceNow / Event Hub | Enterprise incident pipelines |
| Secure webhook | Authenticated webhook (Entra ID) | Locked-down integrations |
The “make it actually stop something” pattern
Here the budget stops being passive. A common dev/test pattern: a budget at 100% on a non-production resource group triggers an Action Group whose Logic App (or Function, or runbook) deallocates the VMs in that RG. The budget did not enforce anything — your automation did, in response to the alert. The flow is threshold crossed → notification → Action Group → automation → action. You own every step after “notification,” which is why a budget is safe everywhere (it can never surprise-stop production) yet can enforce where you explicitly choose.
A caution that has burned people: because budgets evaluate on a cadence and a threshold fires once per period, automation triggered this way is a coarse, periodic signal — fine for “shut the dev box down for the rest of the month,” wrong for second-by-second precision. For real-time reactions use metric alerts, not budgets.
Architecture at a glance
Hold this mental model before the lab — three layers, left to right. On the left is the cost-data plane: every metered event in your scope (VM seconds, GB stored, requests, egress) is rated and accumulated by Cost Management into a running total for the period. This is the same data Cost Analysis shows; the budget reads from it.
In the middle sits the budget — a small config object bound to a scope, holding your amount, reset period, and up to five threshold alerts. On Azure’s evaluation cycle the engine compares the accumulated total and the forecast against each threshold. Nothing here touches resources; it only reads cost and emits a notification event when a line is first crossed in the period. On the right is the notification/action plane — the only place anything leaves the cost system. The notification fans out to the email recipients and contact roles you listed, and/or into an Action Group, which explodes it across its receivers (email, SMS, webhook to Teams, and crucially automation — Logic App / Function / runbook). Automation is the only layer that can change a resource, and only because you wrote it to. End to end: metered usage → accumulated cost → budget evaluates vs threshold → notification → (email | Action Group → automation → optional resource action). The budget is the sensor; enforcement, if any, lives entirely on the right and is entirely yours.
Real-world scenario
Nimbus Learning, a six-person edtech startup in Pune, ran everything on one pay-as-you-go subscription: a couple of App Service apps, an Azure SQL database, a storage account for course videos, and — the problem — a shared “sandbox” resource group where engineers spun up VMs to try things. Steady spend was about ₹48,000/month. One quarter the invoice came in at ₹1,12,000. Finance was furious; engineering had no idea what happened.
The post-mortem was simple. An engineer had created a Standard_D8s_v5 with a premium SSD to benchmark a transcoding idea, got pulled onto a customer fire, and forgot it — it ran 26 days. A second left a pricey Standard_E4s_v5 up for a fortnight. Neither was alarming on any single day (~₹1,300 and ~₹900), so nothing looked wrong until the totals landed. Cost Analysis showed it all in hindsight — but nobody was looking, because nothing told them to.
The fix took an afternoon and cost nothing. They created a subscription-scope monthly budget of ₹60,000 (₹48k normal plus headroom) with four alerts: actual 50/80/100 and forecast 100%. They added a tighter resource-group budget of ₹8,000/month on the sandbox RG, wired to an Action Group with two receivers — a team email and a Logic App that, on the 100% sandbox breach, deallocated every VM in the sandbox RG and posted to Teams naming what it stopped. Production RGs got budgets too, but email only — nothing could ever auto-stop a customer-facing app.
The next month the subscription budget’s forecast-100% alert fired on day 9 — another forgotten sandbox VM pushing the projection toward ₹70,000. By the time the engineer saw the email, the sandbox automation had already deallocated it overnight when the sandbox budget hit 100%. The runaway was caught at roughly ₹6,000 instead of ₹40,000+. The lesson: a budget never stopped anything — but the email it sent, and the one Logic App wired to the sandbox, changed the team’s behaviour and capped the blast radius.
Advantages and disadvantages
The honest trade-off of relying on budgets as your cost guardrail:
| Advantages | Disadvantages |
|---|---|
| Free — budgets cost nothing to create or run | Alert-only by default — never stops spend on its own |
| Fast — a useful budget takes under ten minutes | Not real-time — evaluation cadence + data latency mean lag |
| Scoped flexibly — sub, RG, management group, billing | Management-group / billing budgets need elevated roles |
| Multi-channel via Action Groups — email, SMS, webhook, automation | Enforcement (auto-stop) is your code to build and maintain |
| Forecasted alerts give genuine early warning | Forecasts are noisy early in the period (little data) |
| Deployable as code (Bicep/Terraform) for every sub | Max 5 thresholds per budget; max 50 emails per notification |
| Pairs cleanly with Policy and FinOps tooling | Watches charged cost — very recent usage may not be summed yet |
Where each matters: the alert-only nature is a feature for production (you never want a budget surprise-stopping a customer app) and a limitation for dev/test (so you add automation there deliberately). The latency rules budgets out as a real-time control. And the role requirements at management-group/billing scope mean learners and single teams typically live at subscription/RG scope — which is fine, that is where accountability usually is anyway.
Hands-on lab
This is the centrepiece. You will build a real budget three ways — portal, az CLI, and Bicep — wire it to email and then to an Action Group, validate, and tear it down. Everything here is free (you are billed only if the Action Group actually sends SMS/voice, which we avoid). Use a non-critical subscription or resource group.
Prerequisites for the lab:
- An Azure subscription where you have Owner or Contributor (or at least Cost Management Contributor) at the scope you will target.
- Cloud Shell open (Bash) in the portal, or a local
az≥ 2.50 logged in viaaz login. - A reachable email address for the alert (your own).
- About 15 minutes.
Set the variables you will reuse (run in Cloud Shell):
SUB_ID=$(az account show --query id -o tsv)
RG="rg-budget-lab"
LOCATION="centralindia"
EMAIL="you@example.com" # change to a mailbox you can read
echo "Sub: $SUB_ID RG: $RG"
Create a throwaway resource group to scope a resource-group budget against:
az group create --name "$RG" --location "$LOCATION"
Expected output: JSON with "provisioningState": "Succeeded" and your RG’s id.
Part A — Create a budget in the Azure portal
The portal is the fastest way to see the moving parts, so do this first.
- Search Cost Management (or Subscriptions → your subscription → Budgets). Choose your scope at the top (your subscription, or switch to
rg-budget-lab). - Click + Add. The Create budget form opens.
- Confirm Scope reads your subscription or
rg-budget-lab. Leave Budget type as Cost (not Usage). - Name:
lab-budget-portal. - Set Reset period to Monthly, Creation date to the first of the current month, and leave Expiration date at its default (years out).
- Amount: for the lab use a tiny number so a threshold is easy to cross — e.g. ₹100 (or
1if your currency is USD). In production you set the real figure. Click Next. - On Set alerts, add four threshold rows — Actual 50%, Actual 80%, Actual 100%, Forecasted 100% — each with your email under Alert recipients (email). For each, pick Type, enter % of budget, and the email; optionally add Alert recipients (roles) like Owner. (You can attach an Action Group here under Action groups; we do it via CLI in Part C.)
- Click Create.
Expected result: lab-budget-portal appears in the Budgets list with a progress bar (near 0% on a fresh tiny budget, or already over if your scope has spend — fine, it proves alerts would fire). Click it to see Budget details listing the four alert conditions.
Validation: the row shows amount, current spend vs amount, and an alert-conditions count of 4. There is no “test send” button — Azure fires the email only on a real threshold crossing during an evaluation cycle, so we validate configuration here, not delivery.
Part B — Create the same budget with the az CLI
The CLI is how you make budgets repeatable; the command group is az consumption budget. The cleanest path passes the thresholds via --notifications as a JSON map — build it in a file to keep it readable:
cat > /tmp/notifications.json <<'JSON'
{
"Actual_50": {
"enabled": true, "operator": "GreaterThanOrEqualTo", "threshold": 50,
"thresholdType": "Actual", "contactEmails": ["you@example.com"],
"contactRoles": ["Owner"]
},
"Actual_80": {
"enabled": true, "operator": "GreaterThanOrEqualTo", "threshold": 80,
"thresholdType": "Actual", "contactEmails": ["you@example.com"]
},
"Actual_100": {
"enabled": true, "operator": "GreaterThanOrEqualTo", "threshold": 100,
"thresholdType": "Actual", "contactEmails": ["you@example.com"]
},
"Forecast_100": {
"enabled": true, "operator": "GreaterThanOrEqualTo", "threshold": 100,
"thresholdType": "Forecasted", "contactEmails": ["you@example.com"]
}
}
JSON
# replace the placeholder email with yours
sed -i "s/you@example.com/$EMAIL/g" /tmp/notifications.json
Now create the budget. Use the first of the current month as the start date:
START=$(date -u +%Y-%m-01)
END="2030-12-31"
az consumption budget create \
--budget-name "lab-budget-cli" \
--amount 100 \
--category Cost \
--time-grain Monthly \
--start-date "$START" \
--end-date "$END" \
--notifications @/tmp/notifications.json
Expected output: JSON describing the budget — name: lab-budget-cli, amount: 100.0, category: Cost, timeGrain: Monthly, and a notifications object echoing your four thresholds with enabled: true. (Field names in output are camelCase, e.g. timeGrain, thresholdType.)
Read it back to confirm it exists and the thresholds stuck:
az consumption budget show --budget-name "lab-budget-cli" \
--query "{name:name, amount:amount, grain:timeGrain, alerts:keys(notifications)}" -o json
Expected output: your name and amount, with alerts listing the four notification keys (Actual_50, Actual_80, Actual_100, Forecast_100).
To scope a budget to the resource group instead of the subscription, add --resource-group "$RG" (it sets the budget’s scope to that RG):
az consumption budget create \
--budget-name "lab-budget-rg" \
--resource-group "$RG" \
--amount 50 --category Cost --time-grain Monthly \
--start-date "$START" --end-date "$END" \
--notifications @/tmp/notifications.json
Common CLI parameters at a glance:
| Parameter | Purpose | Notes |
|---|---|---|
--budget-name |
Unique name of the budget | Per scope |
--amount |
The budget figure | In your billing currency |
--category |
Cost or Usage |
You almost always want Cost |
--time-grain |
Monthly / Quarterly / Annually |
The reset period |
--start-date / --end-date |
Active window | Monthly start = first of a month |
--resource-group |
Scopes the budget to an RG | Omit for subscription scope |
--notifications |
Threshold/alert map (inline or @file) |
Up to 5 entries |
Part C — Add an Action Group and attach it
Now make a breach reach a team and (optionally) automation. Create an Action Group with a single email receiver (we skip SMS/voice so the lab stays free):
az monitor action-group create \
--name "ag-cost-alerts" \
--resource-group "$RG" \
--short-name "CostAG" \
--action email costowner "$EMAIL"
Expected output: JSON for the Action Group, including its id (a Microsoft.Insights/actionGroups/... resource ID) and an emailReceivers array with your costowner receiver. Note: --short-name is max 12 characters (it is the prefix on SMS/push) — a longer value errors.
Capture the Action Group ID for the budget:
AG_ID=$(az monitor action-group show --name "ag-cost-alerts" --resource-group "$RG" --query id -o tsv)
echo "$AG_ID"
Attaching an Action Group to a budget is done by adding a contactGroups array (the Action Group resource IDs) to a notification. Rebuild the notifications file with the Action Group wired into the 80%, 100%, and forecast alerts:
cat > /tmp/notifications-ag.json <<JSON
{
"Actual_80": {
"enabled": true, "operator": "GreaterThanOrEqualTo", "threshold": 80,
"thresholdType": "Actual", "contactEmails": ["$EMAIL"],
"contactGroups": ["$AG_ID"]
},
"Actual_100": {
"enabled": true, "operator": "GreaterThanOrEqualTo", "threshold": 100,
"thresholdType": "Actual", "contactEmails": ["$EMAIL"],
"contactGroups": ["$AG_ID"]
},
"Forecast_100": {
"enabled": true, "operator": "GreaterThanOrEqualTo", "threshold": 100,
"thresholdType": "Forecasted", "contactEmails": ["$EMAIL"],
"contactGroups": ["$AG_ID"]
}
}
JSON
az consumption budget update \
--budget-name "lab-budget-cli" \
--amount 100 --category Cost --time-grain Monthly \
--start-date "$START" --end-date "$END" \
--notifications @/tmp/notifications-ag.json
Expected output: the updated budget JSON, with each listed notification now showing a contactGroups array containing your Action Group ID.
Validation: confirm the Action Group is wired in:
az consumption budget show --budget-name "lab-budget-cli" \
--query "notifications.Actual_100.contactGroups" -o json
Expected output: a one-element array holding your ag-cost-alerts resource ID. To extend to automation, add a logicapp, azurefunction, or automationrunbook receiver to the Action Group (not the budget) — the budget keeps pointing at the same Action Group, and the new receiver runs when it fires:
# (Illustrative) add a Logic App receiver to the existing Action Group
az monitor action-group update --name "ag-cost-alerts" --resource-group "$RG" \
--add-action logicapp stopDevVms \
"/subscriptions/$SUB_ID/resourceGroups/$RG/providers/Microsoft.Logic/workflows/stop-dev-vms" \
"https://prod-00.centralindia.logic.azure.com/workflows/.../triggers/manual/paths/invoke"
The Logic App itself (which deallocates the VMs) is out of scope to build here, but this is exactly where the budget-alert-then-automation bridge is made.
Part D — Deploy a budget as Bicep (infrastructure as code)
For repeatability across every subscription, define the budget in Bicep. Budgets are Microsoft.Consumption/budgets; deployed at subscription scope they need targetScope = 'subscription'. Save as budget.bicep:
targetScope = 'subscription'
@description('Budget name')
param budgetName string = 'lab-budget-bicep'
@description('Monthly budget amount in billing currency')
param amount int = 100
@description('Email to notify')
param contactEmail string
@description('Action Group resource IDs to trigger on breach')
param actionGroupIds array = []
@description('First day of the month the budget starts, e.g. 2026-06-01')
param startDate string
resource budget 'Microsoft.Consumption/budgets@2023-11-01' = {
name: budgetName
properties: {
category: 'Cost'
amount: amount
timeGrain: 'Monthly'
timePeriod: {
startDate: startDate
endDate: '2030-12-31'
}
notifications: {
Actual_50: {
enabled: true
operator: 'GreaterThanOrEqualTo'
threshold: 50
thresholdType: 'Actual'
contactEmails: [ contactEmail ]
contactRoles: [ 'Owner' ]
}
Actual_80: {
enabled: true
operator: 'GreaterThanOrEqualTo'
threshold: 80
thresholdType: 'Actual'
contactEmails: [ contactEmail ]
contactGroups: actionGroupIds
}
Actual_100: {
enabled: true
operator: 'GreaterThanOrEqualTo'
threshold: 100
thresholdType: 'Actual'
contactEmails: [ contactEmail ]
contactGroups: actionGroupIds
}
Forecast_100: {
enabled: true
operator: 'GreaterThanOrEqualTo'
threshold: 100
thresholdType: 'Forecasted'
contactEmails: [ contactEmail ]
contactGroups: actionGroupIds
}
}
}
}
Deploy it at subscription scope:
az deployment sub create \
--name "deploy-budget-bicep" \
--location "$LOCATION" \
--template-file budget.bicep \
--parameters contactEmail="$EMAIL" startDate="$START" \
actionGroupIds="['$AG_ID']" amount=100
Expected output: a deployment result with "provisioningState": "Succeeded" and an outputs/properties block; lab-budget-bicep now appears in the Budgets list with all four alerts and the Action Group attached. A resource-group-scoped budget in Bicep is the same resource with targetScope = 'resourceGroup' and deployed via az deployment group create instead.
Part E — Validate the whole setup
Confirm all three budgets and the Action Group exist and are correct:
# List every budget on the subscription scope
az consumption budget list --query "[].{name:name, amount:amount, grain:timeGrain}" -o table
# Show one budget's full alert configuration
az consumption budget show --budget-name "lab-budget-bicep" \
--query "notifications" -o json
# Confirm the Action Group and its receivers
az monitor action-group show --name "ag-cost-alerts" --resource-group "$RG" \
--query "{name:name, enabled:enabled, emails:emailReceivers[].emailAddress}" -o json
Expected output: a table listing lab-budget-portal, lab-budget-cli, lab-budget-bicep (subscription scope) with amounts and Monthly; the chosen budget’s four notifications with their thresholds and types; and the Action Group with enabled: true and your email. In the portal, open each budget and confirm the alert-conditions list and (for the CLI/Bicep ones) the attached Action Group under the alert rows.
A note on testing delivery: you cannot force a budget to send a test email — it fires only on a genuine threshold crossing during Azure’s evaluation cycle. To see a real alert in the lab, set a budget amount below your scope’s current month spend (e.g. ₹1 on a subscription that has already spent anything this month); on the next evaluation cycle (within roughly a day) the actual-50/80/100 alerts will fire and you will receive the emails. Remember to delete that tiny budget afterwards or you will keep getting alerts.
Part F — Teardown
Remove everything so nothing lingers (budgets are free, but tidy is better):
# Delete the three budgets (subscription scope)
az consumption budget delete --budget-name "lab-budget-cli"
az consumption budget delete --budget-name "lab-budget-bicep"
# the portal one:
az consumption budget delete --budget-name "lab-budget-portal"
# Delete the RG-scoped budget
az consumption budget delete --budget-name "lab-budget-rg" --resource-group "$RG"
# Delete the Action Group and the throwaway resource group
az monitor action-group delete --name "ag-cost-alerts" --resource-group "$RG"
az group delete --name "$RG" --yes --no-wait
Expected result: the budgets disappear from the Budgets list; az consumption budget list no longer shows them; the resource group deletion proceeds in the background. Delete any tiny “force a real alert” budget you created in Part E too.
Common mistakes & troubleshooting
The failure modes that make people believe budgets are broken — and the real cause and fix for each:
| # | Symptom | Root cause | How to confirm | Fix |
|---|---|---|---|---|
| 1 | “I set a budget but spend went over and nothing stopped” | A budget alerts, it does not enforce | Re-read the budget — there is no enforce toggle | Wire an Action Group → automation if you want a stop |
| 2 | No alert email ever arrives | Recipient wrong, or mail filtered, or threshold never crossed | Check contactEmails in az consumption budget show; check spam |
Fix the address; lower amount below current spend to test |
| 3 | Alert “late” — fired a day after the spend | Budgets are not real-time; evaluation cadence + data latency | Compare the spike time vs alert time | Expected behaviour; use metric alerts for real-time |
| 4 | Forecast alert fired wildly early then “wrong” | Forecast is noisy early in the period with little data | Watch the forecast tighten over days | Trust actual alerts early; forecast stabilises later |
| 5 | az consumption budget create fails on dates |
Bad date format or monthly start not on the 1st | Check --start-date is YYYY-MM-01, end in future |
Use first-of-month start; future end date |
| 6 | Action Group attached but no notification | Wrong receiver, or --short-name > 12 chars, or AG disabled |
az monitor action-group show → enabled, receivers |
Fix short-name; enable AG; correct receiver target |
| 7 | “Access denied” creating the budget | Missing Cost Management Contributor (or higher) at scope | az role assignment list --assignee <you> --scope <scope> |
Get the cost role on the right scope |
| 8 | Management-group / billing budget won’t create | Needs a billing/management-group role, not just sub Owner | Confirm your role at that scope | Use the correct EA/MCA billing role |
| 9 | Budget shows 0% though resources are running | Cost data lag — recent usage not yet summed | Check Cost Analysis for the same scope/period | Wait for the next data refresh (hours) |
| 10 | Got alerts at 50/80/100 then silence at a higher overrun | Each threshold fires once per period; you only set up to 100% | Look at configured thresholds | Add a >100% (e.g. 120%) threshold to catch overruns |
| 11 | Two budgets fighting / duplicate alerts | Overlapping budgets at sub and RG scope both firing | List budgets at both scopes | Intentional layering is fine; dedupe recipients if noisy |
| 12 | Deleted the budget, alerts kept coming briefly | A queued notification from before deletion | Confirm it stops after one cycle | No action — it settles |
The single most common real ticket is #1 (expecting enforcement) followed by #2/#3 (delivery and timing). Knowing budgets are advisory and lagged removes most of the confusion.
Best practices
Production-grade rules for budgets that actually help:
- Put a subscription-scope budget on every subscription, day one, deployed as code (Bicep/Policy) so new subscriptions get one by default — free, versioned, and your backstop.
- Layer scopes: a subscription backstop plus tighter resource-group budgets where multiple teams/environments share a subscription.
- Always include a forecasted-100% alert — the early warning that catches runaways mid-month, not at the post-mortem.
- Set the amount from real Cost Analysis data, a margin above steady spend, so 80% means “unusual,” not “every month”; re-baseline quarterly as spend grows.
- Use a distribution list, not individuals, in
contactEmailsso alerts survive people leaving — and document who owns each budget’s response. - Reserve automation (auto-stop) for non-production scopes — never wire a production budget to anything that deallocates.
- Add a >100% threshold (e.g. 120%) so a genuine overrun still pings after you cross 100%.
- Treat budgets as detective, not preventive — pair with Azure Policy Deny effects to block expensive SKUs at deploy time.
- Don’t rely on a budget for real-time reaction — for second-by-second spikes, use metric alerts.
Security notes
Budgets touch billing data and can trigger automation, so a few points matter even for a “harmless” alert:
- Least privilege for creation. Grant Cost Management Contributor at the exact scope rather than handing out Owner. Cost data is sensitive — it reveals architecture and scale (see the role boundaries in the Azure Resource Hierarchy).
- Action Group automation runs with real permissions. A Logic App/Function/runbook triggered by a budget executes under its own identity, which may stop or delete resources. Give it only the rights it needs (e.g. deallocate VMs in one RG), and store its secrets in Key Vault, never inline.
- Webhook receivers leak alert content. An alert payload names your scope and spend, so a Teams/Slack webhook URL is a secret — prefer secure webhooks (Entra-authenticated) and rotate any URL that leaks. Send alert emails to controlled distribution lists, not personal mailboxes.
- Don’t let automation DoS you. An over-eager auto-stop on a too-low budget can deallocate resources you needed — test against dev scopes, gate it behind a high threshold, and never point it at production.
Cost & sizing
The good news dominates: budgets and Action Group definitions are free. Creating budgets, adding threshold alerts, and defining an Action Group with email/webhook/Logic-App/Function/runbook receivers all cost nothing — you are not billed for the budget watching spend or the alert being evaluated. Small charges appear only from what an Action Group does when it fires:
| Component | Cost | Notes |
|---|---|---|
| Budget (any scope) | Free | No charge to create or evaluate |
| Action Group definition | Free | Defining receivers costs nothing |
| Email / Azure app push notifications | Free | A generous free allotment, effectively free for alerts |
| SMS / Voice notifications | Charged per message/call | Region-dependent; the only easily-surprising cost — avoid in labs |
| Webhook / Secure webhook | Free (the call itself) | You pay for whatever it triggers |
| Logic App triggered by the AG | Per Logic App action/run pricing | Tiny for occasional cost alerts |
| Azure Function triggered by the AG | Per Functions execution (often within free grant) | Tiny; see Functions pricing model |
| Automation runbook | Per Automation job-minute (free minutes monthly) | Tiny for occasional runs |
Sizing guidance: there is nothing to “size” about the budget itself — it scales to any spend. Size your threshold ladder (a meaningful few, not noisy many) and your automation cost (a Logic App firing a few times a month is rounding-error, well under ₹100/month). The one genuine watch-out is SMS or voice receivers — they bill per message/call and, on a chatty alert, add up. For learners, stick to email + webhook, both effectively free. The whole control — budget + Action Group + email + an occasional Logic App — typically costs ₹0 to under ₹100 a month, while the spend it catches is measured in thousands.
Interview & exam questions
1. What is an Azure budget, and what does it not do? An alerting control: you set an amount, reset period and scope, and Azure notifies you (email or Action Group) when accumulated cost crosses your thresholds. It does not cap or stop spend — resources run past 100%. Enforcement, if wanted, comes from automation you wire to the alert.
2. Difference between actual and forecasted thresholds? Actual fires when real accumulated spend reaches a % (“you’ve spent 80%”). Forecasted fires when Azure’s projection of period-end spend reaches a % (“on track to hit 100%”) and can fire early. Use both: forecast for early warning, actual for the record.
3. At which scopes can you create a budget, and how do roles differ? Subscription and resource group budgets need Cost Management Contributor (or Contributor/Owner) at that scope. Management group and billing account budgets need a management-group or billing role (e.g. Billing account Contributor on EA/MCA), not merely subscription Owner.
4. How do you make a budget alert stop a VM? Point the threshold’s notification at an Action Group containing a Logic App, Function, or runbook receiver; that automation (under its own identity) performs the stop/deallocate. The budget only notifies — the automation does the work.
5. How many thresholds per budget, and emails per alert? Up to 5 thresholds per budget and 50 emails per notification. Plan a small ladder (50/80/100 actual + 100 forecast) and use a distribution list, not many individuals.
6. Why might an alert arrive “late”? Budgets evaluate on a recurring cycle (~8–24h) and usage data lags by hours, so they are not real-time. For second-by-second reactions, use metric alerts.
7. A team set a 100% budget and was shocked spend kept growing. Explain. They assumed enforcement. At 100% Azure sends the alert and nothing stops. To cap spend, use a credit-based spending cap (Free/MSDN only), Azure Policy Deny, or automation wired to the alert.
8. Cost budget vs Usage budget? A Cost budget tracks money (charged cost in your currency) — what almost everyone wants. A Usage budget tracks a quantity of a meter (hours/units). The category is Cost or Usage.
9. How would you deploy the same budget to fifty subscriptions? Define it as Bicep (Microsoft.Consumption/budgets, targetScope = 'subscription') or Terraform and deploy via pipeline, optionally driven by Azure Policy so every new subscription gets it automatically — versioned, no drift.
10. Where do SMS/voice costs come from, and how do you keep a lab free? The budget and Action Group definition are free; SMS/voice receivers bill per message/call. Keep a lab free using only email and webhook receivers.
11. Your budget shows 0% but resources are running — why? Cost-data latency: usage records lag hours-to-a-day, so recent spend may not be summed yet. Cross-check Cost Analysis; it catches up on the next refresh.
12. How do you verify a budget will deliver an alert, with no test button? Temporarily set the amount below the scope’s current-period spend; the next evaluation cycle crosses the actual thresholds and fires. Then delete or re-raise the budget.
These map most directly to AZ-104 (Azure Administrator) — monitor and maintain Azure resources → configure cost management (budgets, alerts, Cost Analysis) — and the cost-management portions of AZ-900 (Fundamentals). The automation/Action-Group angle also touches AZ-204 (alerts and Action Groups) and broader FinOps practice. A compact cert map:
| Question theme | Primary cert | Objective area |
|---|---|---|
| Budget vs cap vs policy; alerts | AZ-104 / AZ-900 | Cost management; governance |
| Action Groups & automation | AZ-104 / AZ-204 | Monitoring & alerting |
| Scope & RBAC for budgets | AZ-104 | Manage cost & access |
| Budgets as code (Bicep) | AZ-104 / AZ-400 | IaC; governance at scale |
Quick check
- Your spend hits 100% of a budget. What does Azure do automatically — and what does it not do?
- You want a heads-up before you overspend, mid-month. Which threshold type do you use, and why can it fire on, say, day 9?
- You’re a subscription Owner but cannot create a budget at the management group. Why, and what do you need?
- A budget alert is wired to an Action Group, but nothing happens on breach. Name two things to check on the Action Group.
- There’s no “send test alert” button. How do you make a budget genuinely fire an alert so you can confirm email delivery?
Answers
- Azure sends the configured notification (email and/or Action Group). It does not stop, throttle, deallocate, or block any resource — a budget is alert-only, never an enforced cap.
- A Forecasted threshold. It fires on Azure’s projection of end-of-period spend, so if early-period spend is high enough that the forecast crosses your % (e.g. 100%), it can alert on day 9 — long before actual spend reaches the amount.
- Subscription Owner does not grant rights at the management-group/billing scope. You need a management-group role (or an EA/MCA billing role like Billing account Contributor) at that scope to create a budget there.
- Check (a) that the Action Group is enabled and its receiver is correct (right email/webhook/Logic-App target), and (b) that the short name is ≤ 12 characters and the receiver type was added properly —
az monitor action-group showlistsenabledand every receiver. - Temporarily set the budget amount below the scope’s current-period spend; on the next evaluation cycle the actual thresholds cross and the alert fires. Delete or re-raise the budget afterward so it stops alerting.
Glossary
- Azure Cost Management budget — a configuration on a scope that watches accumulated cost over a reset period and notifies you when spend crosses defined thresholds; alert-only, not an enforced cap.
- Scope — what a budget watches: a subscription, resource group, management group, or billing account; also determines the role required to create it.
- Reset period (time grain) — Monthly, Quarterly, or Annually; when accumulated cost rolls back to zero.
- Budget amount — the spend figure that thresholds are a percentage of.
- Threshold — an integer percentage (0–1000) of the amount that, when crossed, fires a notification.
- Actual threshold — fires on real, accumulated spend reaching the percentage.
- Forecasted threshold — fires on Azure’s projected end-of-period spend reaching the percentage; an early-warning signal.
- Notification / alert — the email and/or Action Group fired when a threshold is crossed; one notification per threshold per period.
- Action Group — a reusable Azure Monitor resource bundling receivers (email, SMS, push, voice, webhook, Logic App, Function, runbook, ITSM) that an alert triggers.
- Receiver — one channel/action inside an Action Group (e.g. an email receiver, a Logic App receiver).
contactEmails/contactRoles/contactGroups— a notification’s email recipients, RBAC-role recipients (Owner/Contributor/Reader), and attached Action Group IDs.- Cost budget vs Usage budget —
category: Costtracks money;category: Usagetracks a quantity of a meter. - Spending cap — a separate, credit-based control (Free/MSDN subscriptions) that disables resources when credit is exhausted — actual enforcement, unlike a budget.
- Cost Analysis — the Cost Management view showing where spend went; the same data a budget reads.
- Evaluation cadence — the recurring cycle (~8–24h) on which Azure recomputes spend/forecast against thresholds; why budgets are not real-time.
Next steps
You now have a budget that emails you, pings a team via an Action Group, and can bridge to automation — in the portal, CLI and Bicep. Build outward:
- Next: Azure Cost Management for Beginners: Budgets, Alerts and Cost Analysis in Your First 30 Days — the broader first-month cost-hygiene routine this slots into.
- Related: Azure Free Account, Credits and Spending Caps — the one place a hard cap exists, and how it differs from a budget.
- Related: Azure Policy Effects: Deny vs Audit vs Modify vs DeployIfNotExists — the preventive control that actually blocks expensive resources at deploy time.
- Related: Azure Resource Hierarchy: Subscriptions, Resource Groups and Resources — decide where to place budgets and who can create them.
- Related: Azure FinOps and Cost Management: Controlling Cloud Spend at Scale — where budgets fit into an organisation-wide cost-governance practice.