The previous lesson left MercadoFresco's bill at 1,749.60 USD a month, 22 % below the starting point, and with an analysis capable of explaining every line. But all of that is looking backwards. If tomorrow someone brings up a test cluster in the development account and forgets about it, or if a new query takes off on a Friday night, the bill will grow with nothing standing in its way. Nobody has yet put a limit in place.
This lesson moves from analysing to controlling. You will see the difference between Cost Explorer and Budgets and why the billing alarm from 01-02 falls short, the four budget types and their periodicities — including planned budgets for the Christmas campaign — the distinction between thresholds on actual and forecasted cost, the specific budgets MercadoFresco creates by account, service, tag and cost category, how to create them from the console, the CLI and the CDK, the notifications, and the part with teeth: the budget actions capable of freezing the development account when it reaches its limit. At the end, the FinOps routine that sustains the whole cycle.
Cost warning. The first two budgets are free; beyond that they cost 0.02 USD per budget per day, that is, around 0.60 USD a month each. MercadoFresco's ten budgets cost roughly 4.80 USD a month, 0.27 % of the bill, which is probably the best insurance in the catalogue. Budget actions cost extra, around 0.10 USD per action per day. Fictitious data, accounts and identifiers.
Contents
- Analysing versus controlling
- Why the billing alarm from 01-02 falls short
- The four budget types
- Periodicity: fixed, recurring and planned
- Actual cost versus forecasted cost
- Staggered thresholds and who each one notifies
- MercadoFresco's budgets
- Creating a budget from the console, the CLI and the CDK
- The complete JSON, annotated
- Notifications: email, SNS and Slack
- Budget actions
- The development account that freezes itself
- The warning about production
- Budget reports and the monthly review
- Good practices
- When a budget is exceeded for a legitimate reason
- FinOps: inform, optimise and operate
- The monthly cost meeting
- Common mistakes and tips
- Exercises
- Conclusion
Analysing versus controlling
The two tools look alike on screen and are radically different in purpose:
| Cost Explorer (11-03) | AWS Budgets | |
|---|---|---|
| Question it answers | What have we spent on? | Are we going to overshoot? |
| Direction in time | Backwards | Forwards |
| Use | One-off investigation and monthly review | Continuous unattended monitoring |
| Output | Charts and tables for a person | Notifications and actions |
| How often you look | When someone goes in | Never: it tells you |
| Can it prevent spend | No | Yes, with budget actions |
The practical difference is simple: Cost Explorer requires someone to remember to look; Budgets requires nobody to do anything. And in a three-person company, anything that depends on someone remembering ends up failing some month.
It is also worth placing it against the anomaly detection from 11-03, which people confuse it with:
- Anomaly detection: "this spend does not look like your usual pattern". It is statistics, and it has no opinion on whether you can afford it.
- Budgets: "you have gone past the limit you set yourself". It is a business decision expressed as a number.
Both are necessary and they answer different cases. A gradual 3 % monthly rise over six months does not trigger any anomaly — it is perfectly normal month to month — and it does end up blowing the budget. A one-day spike triggers the anomaly and probably barely moves the monthly budget.
Why the billing alarm from 01-02 falls short
In lesson 01-02, with the account freshly created, a CloudWatch billing alarm was configured on the EstimatedCharges metric with a threshold of 10 USD, along with the presupuesto-mensual-mercadofresco budget for the same amount. That was the right thing then and today it is useless, for five reasons:
- The amount is obsolete. It has been firing on the 2nd of every month for twenty-odd months. An alarm that is always red is not an alarm: it is noise, and nobody reads its emails any more.
- It is global. A single number for the whole organisation does not tell you where the problem is.
- It only looks at actual cost. It finds out once the money is spent, not when it is about to be spent.
- The
EstimatedChargesmetric only exists inus-east-1and belongs to the management account: it cannot watch a member account separately. - It cannot do anything. It notifies and that is all.
The first action in this lesson is therefore to retire that budget: raise its amount to the real value and turn it into the organisation-wide budget. It is not deleted — it keeps its history — but it stops being a relic.
The four budget types
| Type | What it measures | Example at MercadoFresco |
|---|---|---|
Cost (COST) |
Money spent in a period | "Development must not go above 170 USD a month" |
Usage (USAGE) |
Quantity of a specific usage type | "No more than 900 GB processed by the NATs a month" |
Savings Plans (SAVINGS_PLANS_UTILIZATION / _COVERAGE) |
What percentage of the commitment is used and what percentage of usage is covered | "Warn me if utilisation drops below 95 %" |
Reservations (RI_UTILIZATION / RI_COVERAGE) |
The same for reserved instances | "Warn me if ElastiCache coverage drops below 80 %" |
The first two are the ones used day to day. The last two exist because a poorly used commitment is money lost silently, and they become essential from 11-05 onwards.
The usage budget is the least known and it solves a problem the cost budget cannot see: if a service's price goes down, a cost budget stops warning you even though consumption has shot up. MercadoFresco uses it for the NAT Gateways, where the volume of data processed is an indicator of architectural health as well as a cost.
Periodicity: fixed, recurring and planned
| Periodicity | Behaviour | When to use it |
|---|---|---|
| Monthly recurring | The same amount every month, counter reset on the 1st | The normal case; 8 of MercadoFresco's 10 |
| Quarterly / annual | Accumulates across the whole period | Project or financial-year budgets |
Fixed (FIXED) |
A total amount for an interval with an end date | A migration, a proof of concept, a contract |
Planned (PLANNED) |
Different amounts per month, defined in advance | Known seasonality |
The planned budget is what solves MercadoFresco's real problem: in December, the Christmas campaign multiplies orders and with them the spend. With a monthly recurring budget of 2,000 USD, December trips every threshold and the team learns to ignore them in exactly the month when attention matters most.
MercadoFresco's planned budget:
| Month | Amount | Reason |
|---|---|---|
| January to October | 2,000 USD | Normal operation |
| November | 2,300 USD | Campaign preparation, load testing |
| December | 2,600 USD | Campaign: ×1.6 orders in the first three weeks |
| The following January | 2,100 USD | Campaign tail and returns |
And with this you gain something more valuable than a well-calibrated alert: the November conversation. Putting a number on December forces you to estimate it, and estimating it forces you to talk to the business about how many orders are expected. A budget is, before it is a control, an exercise in forecasting.
Actual cost versus forecasted cost
Each threshold in a budget can be evaluated against two different things, and you have to configure both:
| Threshold type | When it fires | What it gives you |
|---|---|---|
Actual cost (ACTUAL) |
When what has been spent reaches the threshold | Certainty: it has already happened |
Forecasted cost (FORECASTED) |
When the end-of-month projection reaches the threshold | Anticipation: it can still be avoided |
The example that makes it clear. A development budget of 170 USD a month:
- On the 9th, 51 USD has been spent. The actual 80 % threshold (136 USD) does not fire: there is a long way to go.
- That same day, the end-of-month forecast is 170 USD, because the daily rate has been climbing since the 6th. The forecasted 100 % threshold does fire.
- There are 21 days left to correct it. That is the whole difference between finding out in time and finding out too late.
Two warnings about the forecast, which is not magic:
- It needs history. A freshly created budget, or a new account, does not produce reliable forecasts for the first few weeks. AWS needs on the order of five weeks of data.
- It extrapolates. If a one-off migration on the 3rd consumed a lot, the forecast will believe that repeats all month and will fire a false alarm. You learn to recognise them.
MercadoFresco's standard configuration combines both: forecasted at 80 % and at 100 % to anticipate, actual at 100 % to confirm, and in the case of development, actual at 100 % with an automatic action.
Staggered thresholds and who each one notifies
A budget with a single threshold at 100 % warns you when there is nothing left to do. One with five thresholds generates so much noise that all of them get ignored. Three is the number that works:
| Threshold | Type | Meaning | Who receives it | What is expected |
|---|---|---|---|---|
| 50 % | Actual | We are halfway through at mid-month: normal | Nobody by email; the dashboard only | Nothing |
| 80 % | Forecasted | We are going to graze the limit | Marta, by SNS to alertas-mercadofresco |
Look at the breakdown this week |
| 100 % | Forecasted | We are going to exceed it | Marta + the account owner | Decide: fix it or raise the budget |
| 100 % | Actual | We have already exceeded it | Marta + the manager | Written explanation in the monthly review |
| 120 % | Actual | It has got out of hand | Everyone, and an action in development | Immediate intervention |
The logic of the escalation rests on a principle: each threshold must have a different recipient and a different expected action. If the 50 % and the 80 % notify the same people to do the same thing, one of the two is redundant. And MercadoFresco's 50 % deliberately emails nobody: reaching half the budget at mid-month is exactly what should happen.
MercadoFresco's budgets
Ten budgets, all consistent with the 1,749.60 USD bill that followed the optimisations in 11-03:
| Budget | Scope | Current spend | Amount | Thresholds | Action |
|---|---|---|---|---|---|
presupuesto-mensual-mercadofresco |
The whole organisation | 1,749.60 USD | 2,000 USD | 80 % and 100 % forecasted, 100 % actual | No |
pres-mf-produccion |
Account 111122223333 |
1,266.10 USD | 1,400 USD | 80 % and 100 % forecasted, 100 % actual | No, never |
pres-mf-preproduccion |
Account 222233334444 |
245.00 USD | 290 USD | 80 % and 100 % forecasted | No |
pres-mf-desarrollo |
Account 333344445555 |
143.30 USD | 170 USD | 80 % forecasted, 100 % actual | Yes: freeze |
pres-mf-herramientas |
Account 555566667777 |
51.40 USD | 70 USD | 100 % forecasted | No |
pres-mf-gobierno |
Accounts 4444… and 9999… |
43.80 USD | 55 USD | 100 % forecasted | No |
pres-mf-analitica |
Tag Componente=analitica |
187.50 USD | 220 USD | 80 % and 100 % forecasted | No |
pres-mf-cloudwatch |
CloudWatch service, all accounts | 140.90 USD | 170 USD | 100 % forecasted | No |
pres-mf-nat-uso |
Usage: GB processed by the NATs | 712 GB | 900 GB | 90 % actual | No |
pres-mf-navidad |
Organisation, planned | — | 2,000-2,600 USD | 100 % forecasted | No |
Four design decisions in this table deserve explaining:
The margin over current spend is between 10 and 20 %. Neither tight — it would fire every month on normal variation — nor generous — it would never warn you. MercadoFresco's rule: the budget is set 15 % above the actual spend of the last three months, and it is reviewed every quarter.
The sum of the per-account budgets (1,985 USD) is lower than the global one (2,000 USD). That is deliberate: if every environment approaches its limit at once, the global one also warns. The other way round — per-team budgets that add up to more than the global one — is the classic mistake that means nobody ever feels addressed.
Development is the strictest and the only one with an action. Its margin is 18 % but its action threshold is actual cost at 100 %, not forecasted, so as not to freeze the account on a wrong forecast. It is the environment where a mistake costs little to fix and where most experiments happen.
Some budgets overlap on purpose. The analitica one cuts across accounts and the CloudWatch one cuts across services: both capture spend that is already counted in the per-account budgets. There is nothing wrong with counting twice when the purpose is to watch two different dimensions. What you must not do is add them together.
Creating a budget from the console, the CLI and the CDK
From the console, under Billing → Budgets → Create budget, the flow is: template or custom → type → periodicity and amount → filters (account, service, tag, category) → thresholds and recipients → actions. It is convenient for the first one and inadvisable for all ten: a budget created by hand is not in Git, is not reviewed by pull request and disappears if someone deletes it.
From the CLI, with two JSON files:
aws budgets create-budget \
--account-id 999988887777 \
--budget file://pres-mf-desarrollo.json \
--notifications-with-subscribers file://pres-mf-desarrollo-avisos.jsonWith the CDK, which is how MercadoFresco really maintains them, in the mercadofresco-infra repository:
from aws_cdk import Stack, aws_budgets as budgets
from constructs import Construct
class BudgetsStack(Stack):
def __init__(self, scope: Construct, construct_id: str, **kwargs):
super().__init__(scope, construct_id, **kwargs)
ALERTS_TOPIC = "arn:aws:sns:eu-west-1:111122223333:alertas-mercadofresco"
def budget(name, amount, account=None, tag=None,
service=None, with_action=False):
filters = {}
if account:
filters["LinkedAccount"] = [account]
if tag:
filters["TagKeyValue"] = [f"user:Componente${tag}"]
if service:
filters["Service"] = [service]
notifications = [
# 80 % forecasted: early warning
budgets.CfnBudget.NotificationWithSubscribersProperty(
notification=budgets.CfnBudget.NotificationProperty(
comparison_operator="GREATER_THAN",
notification_type="FORECASTED",
threshold=80, threshold_type="PERCENTAGE"),
subscribers=[budgets.CfnBudget.SubscriberProperty(
address=ALERTS_TOPIC, subscription_type="SNS")]),
# 100 % actual: confirmation
budgets.CfnBudget.NotificationWithSubscribersProperty(
notification=budgets.CfnBudget.NotificationProperty(
comparison_operator="GREATER_THAN",
notification_type="ACTUAL",
threshold=100, threshold_type="PERCENTAGE"),
subscribers=[budgets.CfnBudget.SubscriberProperty(
address=ALERTS_TOPIC, subscription_type="SNS")]),
]
return budgets.CfnBudget(
self, name,
budget=budgets.CfnBudget.BudgetDataProperty(
budget_name=name,
budget_type="COST",
time_unit="MONTHLY",
budget_limit=budgets.CfnBudget.SpendProperty(
amount=amount, unit="USD"),
cost_filters=filters or None,
cost_types=budgets.CfnBudget.CostTypesProperty(
include_tax=False, # taxes cannot be optimised
include_credit=False, # credits mask the spend
include_refund=False,
use_amortized=True, # consistent with 11-03
),
),
notifications_with_subscribers=notifications,
)
budget("presupuesto-mensual-mercadofresco", 2000)
budget("pres-mf-produccion", 1400, account="111122223333")
budget("pres-mf-preproduccion", 290, account="222233334444")
budget("pres-mf-desarrollo", 170, account="333344445555",
with_action=True)
budget("pres-mf-herramientas", 70, account="555566667777")
budget("pres-mf-analitica", 220, tag="analitica")
budget("pres-mf-cloudwatch", 170, service="AmazonCloudWatch")Three details in the code that head off frequent errors:
TagKeyValuewith the formatuser:Key$Value. That$as a separator and theuser:prefix are mandatory and they do not appear in the most obvious place in the documentation. With the syntax written wrongly, the budget is created without error and filters down to zero, so it never warns you.include_tax=Falseandinclude_credit=False. Consistent with the decision in 11-03: the working number is the service cost. With taxes included, a 2,000 USD budget would fire at 1,653 USD of actual spend and nobody would know why.use_amortized=True. Today it changes nothing because there are no commitments, but from 11-05 onwards it does: without amortising, the month a Savings Plan is paid upfront would trip every threshold at once.
The complete JSON, annotated
The CLI equivalent of the development budget, which is the most complete because it includes an action:
{
"BudgetName": "pres-mf-desarrollo",
"BudgetType": "COST",
"TimeUnit": "MONTHLY",
"BudgetLimit": { "Amount": "170", "Unit": "USD" },
"CostFilters": {
"LinkedAccount": ["333344445555"]
},
"CostTypes": {
"IncludeTax": false,
"IncludeSubscription": true,
"IncludeRefund": false,
"IncludeCredit": false,
"IncludeUpfront": true,
"IncludeRecurring": true,
"IncludeOtherSubscription": true,
"IncludeSupport": true,
"IncludeDiscount": true,
"UseAmortized": true,
"UseBlended": false
}
}And the notifications file, which is a separate file and not a property of the previous one:
[
{
"Notification": {
"NotificationType": "FORECASTED",
"ComparisonOperator": "GREATER_THAN",
"Threshold": 80,
"ThresholdType": "PERCENTAGE",
"NotificationState": "ALARM"
},
"Subscribers": [
{ "SubscriptionType": "SNS",
"Address": "arn:aws:sns:eu-west-1:111122223333:alertas-mercadofresco" },
{ "SubscriptionType": "EMAIL", "Address": "[email protected]" }
]
},
{
"Notification": {
"NotificationType": "ACTUAL",
"ComparisonOperator": "GREATER_THAN",
"Threshold": 100,
"ThresholdType": "PERCENTAGE"
},
"Subscribers": [
{ "SubscriptionType": "SNS",
"Address": "arn:aws:sns:eu-west-1:111122223333:alertas-mercadofresco" },
{ "SubscriptionType": "EMAIL", "Address": "[email protected]" }
]
}
]The fields that matter:
ThresholdTypeacceptsPERCENTAGEorABSOLUTE_VALUE. The percentage survives changes to the budget amount; the absolute one has to be updated by hand and gets forgotten.ComparisonOperatoracceptsGREATER_THAN,LESS_THANandEQUAL_TO. TheLESS_THANhas a little-known legitimate use: warning that the utilisation of a Savings Plan has fallen below a minimum, which is exactly what will be needed in 11-05.IncludeSupport: trueandIncludeSubscription: trueare left on: the support fee and the subscriptions are real spend that has to be controlled, unlike taxes.- You can mix SNS and email recipients in the same threshold. Ten subscribers maximum per notification.
Notifications: email, SNS and Slack
Three channels, with different roles:
| Channel | Advantage | Drawback | Use at MercadoFresco |
|---|---|---|---|
| Needs no configuration | Gets lost among the rest; nobody reads it on a Friday | 100 % actual thresholds, as a record | |
| SNS | Integrates with everything: Lambda, queues, Chatbot | Requires a topic policy | The main channel |
| Chatbot → Slack/Teams | Arrives where the team is already looking | One more channel that can be muted | 80 % and 100 % forecasted thresholds |
For Budgets to be able to publish to the alertas-mercadofresco topic you have to authorise it explicitly in the topic policy, and it is the step most often forgotten:
{
"Sid": "PermitirPublicarABudgets",
"Effect": "Allow",
"Principal": { "Service": "budgets.amazonaws.com" },
"Action": "SNS:Publish",
"Resource": "arn:aws:sns:eu-west-1:111122223333:alertas-mercadofresco",
"Condition": {
"StringEquals": { "aws:SourceAccount": "999988887777" },
"ArnLike": { "aws:SourceArn": "arn:aws:budgets::999988887777:budget/*" }
}
}The aws:SourceAccount and aws:SourceArn conditions are not decoration: without them, any budget in any AWS account could publish to that topic. It is the confused deputy protection pattern already seen in 04-01.
The result in Slack, through the #mercadofresco-alertas channel the team has used since module 5, has an added advantage over email: it is public within the team. An alert everyone can see gets dealt with; one that arrives in a shared inbox does not.
Budget actions
Here Budgets stops being an alerting system and becomes a control. A budget action runs when a threshold is reached and can do three things:
| Action | What it does | Reversible |
|---|---|---|
| Apply an IAM policy | Attaches a policy — usually restrictive — to users, groups or roles | Yes, by removing it |
| Apply a service control policy (SCP) | Attaches an SCP to an OU or account | Yes |
| Stop instances | Stops specific EC2 instances or RDS clusters | Yes, by starting them |
And in two modes:
- Automatic (
AUTOMATIC): it runs on its own when the threshold is reached. - Manual (
MANUAL): it notifies and waits for an authorised person to approve it from the console.
The rule that governs the choice is simple and admits no convenient exceptions: automatic only where the worst case is an inconvenience; manual where the worst case is an incident.
The development account that freezes itself
MercadoFresco's specific case: account 333344445555 has a 170 USD budget and, on reaching 100 % of actual cost, an SCP that prevents creating new resources is attached to it automatically.
The policy that is applied:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "CongelarCreacionDeRecursosCaros",
"Effect": "Deny",
"Action": [
"ec2:RunInstances",
"ec2:CreateVolume",
"rds:CreateDBInstance",
"rds:CreateDBCluster",
"eks:CreateCluster",
"elasticache:CreateReplicationGroup",
"redshift-serverless:CreateWorkgroup",
"ecs:CreateService",
"elasticloadbalancing:CreateLoadBalancer",
"sagemaker:CreateNotebookInstance"
],
"Resource": "*"
}
]
}And its configuration as an action:
aws budgets create-budget-action \
--account-id 999988887777 \
--budget-name pres-mf-desarrollo \
--notification-type ACTUAL \
--action-type SCP \
--action-threshold '{"ActionThresholdValue": 100, "ActionThresholdType": "PERCENTAGE"}' \
--approval-model AUTOMATIC \
--execution-role-arn arn:aws:iam::999988887777:role/rol-mercadofresco-acciones-presupuesto \
--definition '{
"ScpActionDefinition": {
"PolicyId": "p-mfcongelar01",
"TargetIds": ["333344445555"]
}
}' \
--subscribers '[
{"SubscriptionType": "SNS",
"Address": "arn:aws:sns:eu-west-1:111122223333:alertas-mercadofresco"}
]'The decisions that make this safe rather than a bomb:
- Only the creation of expensive resources is denied. What is already running keeps running: nobody loses work in progress. You can still deploy code, read logs and query databases. The only thing you cannot do is switch on something new and expensive.
- It fires on actual cost, not forecasted. Freezing an account on a wrong forecast would be unacceptable even in development.
- The SCP is applied only to account
333344445555, not to the whole OU. A mistake inTargetIdspointing at theCargasOU would freeze production too, and that is exactly the kind of accident you have to design out of existence. - The execution role has minimum permissions: only
organizations:AttachPolicyon that policy and that account. - There is a written unblocking procedure in
mercadofresco-infra/docs/runbooks/, saying who can run it (Marta) and what gets documented afterwards. An automatic action with no documented reversal procedure is a trap for the team.
The real effect observed in the first month: on 24 September, the account reached 170 USD because Luis had brought up an EKS cluster for the exercise in 10-03 and left it on over a weekend. The SCP was applied, Luis saw the alert in Slack, deleted the cluster and Marta removed the SCP within ten minutes. Cost of the incident: about 40 USD. Without the budget, the cluster would have stayed on until the monthly review: about 220 USD.
The warning about production
This needs saying with complete clarity because it is the gravest mistake you can make with this tool:
Never apply a restrictive automatic action to the production account without understanding exactly what it breaks.
The scenario that explains it: on a campaign Friday, orders take off, spend rises and the production budget hits 100 %. The automatic action denies ecs:CreateService and elasticloadbalancing:CreateLoadBalancer. So far, perhaps harmless. But if the policy included ec2:RunInstances or any action used by auto scaling, the system could not scale precisely at the moment it is most needed, and a decision to save 300 USD would cause an outage costing far more than that.
That is why MercadoFresco's pres-mf-produccion budget:
- Has no automatic action. None.
- Has a manual action of reinforced notification only at 120 %, which requires explicit approval and which in practice is a reminder that someone needs to look.
- Its margin is larger than the rest, because a good sales month must fit inside it without raising an alarm.
And the general principle that follows: cost control must never be able to degrade the service to customers. An exceeded budget is fixed with a decision, not with an automatic cut-off.
Budget reports and the monthly review
AWS Budgets Reports lets you schedule a periodic report — daily, weekly or monthly — with the status of up to 50 budgets, emailed to a maximum of 50 recipients. It costs 0.01 USD per report sent.
MercadoFresco has one:
| Parameter | Value |
|---|---|
| Name | informe-mensual-presupuestos-mf |
| Frequency | Monthly, on the 3rd |
| Budgets included | All ten |
| Recipients | Marta, Luis, Sara and the manager |
| Cost | 0.01 USD a month |
Its purpose is not to inform — the team already gets alerts in Slack — but to open the monthly meeting with a common document. Having all four people look at the same PDF before sitting down saves the first fifteen minutes of any cost meeting.
Good practices
The seven rules MercadoFresco writes into its cost management document:
- One budget per unit with an owner, not just a global one. A global budget tells you there is a problem; one per account or per component tells you where it is. The global one without the specific ones is nearly useless.
- Margins of 10 to 20 % over actual spend in the last three months. Tight generates false alarms; generous never warns you.
- Quarterly review of every amount, coinciding with the review of a Well-Architected pillar. A budget that has not been touched for a year is wrong, whether it is too high or too low.
- Forecasted thresholds to act, actual ones to confirm. Always both.
- Everything in code. The ten budgets live in
mercadofresco-infraand are deployed by the pipeline. One created by hand in the console vanishes without trace when someone deletes it. - Automatic actions only where the worst case is an inconvenience. Development yes, pre-production maybe, production never.
- An exceeded budget always generates a written line, even if the conclusion is "it is normal, we are raising the amount". Without that record, in six months nobody will remember why the budget is what it is.
When a budget is exceeded for a legitimate reason
This is the most common case and the worst handled. The business grows, orders rise 35 %, the bill rises 20 % and the budget trips. There is no error, no forgotten resource and no inefficiency: the number was simply out of date.
MercadoFresco's procedure, in four steps:
- Check the unit cost before the total. If the cost per order has fallen or held steady, the growth is healthy and the conversation is a different one. If it has risen, there is something more than growth going on.
- Identify the specific cause. "Orders are up" is not enough; you need to see which lines of the bill have grown and check that the ones growing are the ones that should — Fargate, Aurora, queues — and not the ones that should not — logs, transfer, orphans.
- Decide explicitly: raise the budget, optimise, or both. The decision is taken by whoever owns the budget, not by whoever watches it.
- Document the change of amount with its date and its reason in the same repository where the budget lives. The commit is the record.
What you must not do, and is done constantly: raise the amount in the console without telling anyone. A year later, nobody knows why the production budget is 3,400 USD or who decided it, and the number has stopped meaning anything.
FinOps: inform, optimise and operate
Everything covered in the last three lessons has a name in the industry: FinOps, the discipline of managing cloud spend as a shared responsibility between technology, finance and the business. Its model has three phases that repeat in a cycle:
graph LR I["INFORM<br/>Visibility and allocation<br/>11-02 and 11-03"] --> O["OPTIMISE<br/>Reduce and commit<br/>11-03 and 11-05"] O --> P["OPERATE<br/>Govern and automate<br/>11-04"] P --> I
| Phase | What happens | Where it was seen | Status at MercadoFresco |
|---|---|---|---|
| Inform | Tagging, allocation, unit cost, dashboards | 11-02, 11-03 | Done |
| Optimise | Switch off what is spare, size, commit | 11-03, and 11-05 | First pass done |
| Operate | Budgets, policies, actions, routine | 11-04 | Under way |
The three principles that hold the model up and are worth keeping in sight:
- Teams own their spend. There is no one person controlling everybody's money: there is distributed information and local responsibility. That is why the split by
Propietarioin 11-02 matters. - Decisions are taken on business value, not on absolute cost. Spending more can be the right decision. The question is never "how do we spend less?" but "are we getting value for what we spend?".
- A central team enables, it does not control. At MercadoFresco that team is Marta with two hours a month. In a large company it would be a team, but its role is the same: providing tools and context, not approving spending.
Who takes part, in a three-person company:
| Person | FinOps role | Concrete responsibility |
|---|---|---|
| Marta | FinOps practitioner and technical lead | Maintains budgets and tags; convenes the meeting; decides the technical optimisations |
| Luis | Engineer, owner of his spend | Answers for the cost of the shop and of the non-production accounts |
| Sara | Business and data | Provides the order volume for the unit cost; answers for analitica |
| Manager | Budget and priority | Approves amounts, commitments and the acceptable level of risk |
The monthly cost meeting
Marta establishes a 30-minute meeting on the 5th of each month, with a fixed script:
| Minutes | Content | Who |
|---|---|---|
| 0-3 | Cost per order for the month and its variation | Marta |
| 3-8 | Total and split by environment against the budget | Marta |
| 8-15 | The three lines that have risen most and why | Luis and Sara |
| 15-20 | Anomalies and budgets exceeded since the last meeting | Marta |
| 20-25 | One action for the month, with an owner and a date | Everyone |
| 25-30 | Forecast for the current month and warnings for the next | Marta |
The three rules that let the meeting survive more than three months:
- Start with the unit cost, not the total. It changes the tone of the conversation from "we spend a lot" to "we spend well or badly".
- One action a month. Twelve actions a year that get done are worth more than forty that get abandoned.
- Nobody gets a surprise in public. If someone's spend has shot up, it gets discussed beforehand. A cost meeting that turns into a tribunal stops being held.
Common Mistakes and Tips
Mistake: a single global budget. It tells you there is a problem and not where. Tip: one per account as a minimum, and one per critical component. The global one is the ceiling, not the instrument.
Mistake: per-team budgets that add up to more than the global one. Each one thinks it is doing fine and the total takes off without anybody feeling addressed. Tip: the sum of the specific ones should stay below the global one, leaving margin.
Mistake: using only actual cost thresholds. You find out when there is nothing left to do. Tip: forecasted at 80 % and at 100 % to anticipate, actual at 100 % to confirm.
Mistake: leaving the 10 USD budget from the first lesson. It fires on the 2nd of every month, it gets ignored, and with it every other alert on the same channel gets ignored. Tip: a budget that is always red does active harm. Update it or remove it.
Mistake: including taxes and credits in the amount. The budget fires at a much lower actual spend and nobody understands why. Tip: IncludeTax=false and IncludeCredit=false, consistent with the analysis in 11-03.
Mistake: writing the tag filter wrongly. The format is user:Key$Value; with any other syntax the budget is created without error and filters down to zero, so it never warns you. Tip: after creating it, check in the console that it shows a current spend other than zero.
Mistake: an automatic action in production. On a campaign Friday, the policy denies scaling exactly when it is needed. Tip: production never; and if it is ever done, make sure the policy touches nothing that auto scaling uses.
Mistake: forgetting the SNS topic policy. The budget is created, the thresholds are reached and nothing arrives. Tip: add the permission for budgets.amazonaws.com with aws:SourceAccount, and test the alert by temporarily creating a 0.01 USD budget.
Mistake: raising the amount in the console when it fires. Six months later nobody knows why the budget is what it is. Tip: the amount lives in the CDK; changing it is a commit with its reason.
Tip: create the budget before creating the resources. A budget is an agreed limit, not an after-the-fact summary. When a new environment is opened, the first thing deployed is its budget.
Tip: use a usage budget for whatever you do not want to grow, even if it is cheap today: GB through the NAT, GB ingested into CloudWatch, requests to the Cost Explorer API. The price can come down; runaway consumption is still a symptom.
Exercises
Exercise 1: designing the set of budgets
A fictitious company, RopaCircular, sells second-hand clothing. It has three accounts — production (2,800 USD/month), pre-production (600 USD/month) and data (900 USD/month) — and a team of eight people. In November, Black Friday triples its sales. The manager has asked for "things not to get out of hand" and has given an annual ceiling.
- Propose the set of budgets with their amounts, types and periodicities.
- State which thresholds you would put on each one and who they would notify.
- In which account would you put an automatic action and with exactly which policy?
- How would you handle November?
Exercise 2: interpreting an alert
On 11 October this alert arrives at alertas-mercadofresco:
AWS Budgets: pres-mf-produccion Threshold: 100 % (FORECASTED) Budget: 1,400.00 USD Current spend: 612.40 USD Forecast: 1,482.00 USD
- Is it cause for alarm? Reason it out with the data available.
- List three checks you would make, in order.
- If it turns out that October's orders are running 22 % above September's, what decision would you take and what would you document?
Exercise 3: designing a safe budget action
You are asked to apply an automatic action to the pre-production account 222233334444, with a budget of 290 USD, to stop it taking off as development once did.
- Write the policy you would apply, justifying what you include and what you leave out.
- Choose between an actual or forecasted threshold and between automatic or manual mode, with justification.
- List three things that could go wrong and how you would mitigate them.
Solutions
Solution to exercise 1
(1) Proposed set of budgets:
| Budget | Scope | Type | Periodicity | Amount |
|---|---|---|---|---|
rc-global |
Organisation | Cost | Planned | 4,800 USD (Nov: 8,500) |
rc-produccion |
Production account | Cost | Planned | 3,200 USD (Nov: 6,500) |
rc-preproduccion |
Pre-production account | Cost | Monthly | 700 USD |
rc-datos |
Data account | Cost | Monthly | 1,050 USD |
rc-anual |
Organisation | Cost | Annual | The manager's ceiling |
Five budgets with margins of between 14 % and 17 %. The sum of the three per-account ones (4,950 USD) comes out slightly above the global 4,800, which in this case is acceptable because the global one is planned and acts as a monthly ceiling; if you want the strict pattern, raise the global to 5,200.
rc-anual deserves a separate comment: it is the one that literally translates what the manager asked for. An annual budget accumulates across the whole financial year and warns when the sum of the months elapsed points to exceeding the ceiling. It is the only one that answers the question "are we going to come in on budget for the year?", which no monthly budget can answer.
(2) Thresholds and recipients:
| Budget | Thresholds | Recipients |
|---|---|---|
rc-global |
80 % forecasted, 100 % forecasted, 100 % actual | Technical lead; the manager only at 100 % actual |
rc-produccion |
80 % and 100 % forecasted | Technical lead + product owner |
rc-preproduccion |
80 % forecasted, 100 % actual | Technical lead |
rc-datos |
80 % and 100 % forecasted | Technical lead + data team |
rc-anual |
50 %, 75 % and 90 % actual | Manager and technical lead |
Note that the annual one uses actual and lower thresholds: with a twelve-month horizon, reaching 75 % in September is already an actionable signal.
(3) Automatic action: only in pre-production, and with this policy:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Deny",
"Action": ["ec2:RunInstances", "rds:CreateDBInstance", "rds:CreateDBCluster",
"eks:CreateCluster", "elasticache:CreateReplicationGroup",
"sagemaker:CreateNotebookInstance"],
"Resource": "*"
}]
}Creating expensive resources is denied and nothing existing is touched. In production no action is set at all, for the usual reason: the worst case is an outage during Black Friday, which costs vastly more than the overspend it would avoid. In data none either: a load process interrupted halfway can leave the warehouse inconsistent, which is an incident and not an inconvenience.
(4) November. With a planned budget that assigns November an amount in line with triple the sales — around 8,500 USD globally — and with two accompanying measures: raising the anomaly thresholds during the campaign, because the pattern changes and the detector will generate false positives; and temporarily disabling pre-production's automatic action during Black Friday week, since that is when most testing happens and a freeze at that moment would block the team exactly when it cannot afford it. Both measures are documented with a reversal date, so nobody forgets to switch them back on in December.
Solution to exercise 2
(1) Is it cause for alarm? Not for alarm, but for attention. The data says this: by the 11th, 43.7 % of the budget has been consumed, when the proportional figure would be 35.5 %. The forecast of 1,482 USD exceeds the budget by 5.9 %, which is a small margin and within what a forecast can get wrong. Besides, the alert is of the forecasted type, not actual: there are 20 days left to act. Ignoring it would be a mistake, and so would panicking.
(2) Three checks, in order:
- The cost per order. If October has more orders than September and the unit cost is holding or falling, the growth is healthy and the conversation becomes one about the budget amount, not about a technical problem. It is always the first check.
- Daily view of the month in Cost Explorer, filtered to the production account. You are looking for a step: if daily spend jumped on one specific day and stayed there, there is a new resource or a configuration change; if the rise is gradual, it is business volume.
- Breakdown by service compared with the previous month, looking at the absolute value of the variation. If Fargate, Aurora and the queues are rising, it is real activity. If CloudWatch or data transfer rises, or a service appears that was not there before, it is something else.
(3) Decision and documentation. With orders 22 % higher and the budget projected 5.9 % over, the cost per order is falling noticeably: the system absorbs 22 % more business with 6 % more spend. It is the best possible news.
The right decision is to raise the production budget from 1,400 to 1,600 USD, and to make no emergency optimisations. What gets documented, in the commit that changes the amount in the CDK:
Raise pres-mf-produccion from 1,400 to 1,600 USD Reason: sustained business growth. October orders are running 22 % above September and the cost per order is falling from 0.00972 to 0.00891 USD. The previous budget was set in August on a volume of 180,000 orders per month; the current volume is ~220,000. Amount review: January, after the Christmas campaign. Approved by: management, 2026-10-12.
With two notes that head off future problems: you also need to review the global budget of 2,000 USD, because if production goes up to 1,600 the sum of the specific ones gets too close to the ceiling; and it is worth noting the January review, because October's figure incorporates growth that may not be permanent.
Solution to exercise 3
(1) The policy. Pre-production has a particularity that sets it apart from development: it has to resemble production, and the pipeline deploys to it automatically. That conditions what you can deny:
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "CongelarSoloLoCaroYNoAutomatizado",
"Effect": "Deny",
"Action": [
"ec2:RunInstances",
"rds:CreateDBInstance",
"rds:CreateDBCluster",
"eks:CreateCluster",
"elasticache:CreateReplicationGroup",
"redshift-serverless:CreateWorkgroup",
"sagemaker:CreateNotebookInstance"
],
"Resource": "*"
}]
}What is included is the creation of databases, clusters, caches and instances, which in pre-production are created by hand for one-off tests and are what drives the bill. What is deliberately left out is ecs:CreateService, ecs:RunTask, elasticloadbalancing:*, lambda:* and cloudformation:*, because all of that is used by the pipeline on every deployment: denying it would break continuous integration, and a team that cannot deploy to pre-production ends up deploying to production without testing, which is infinitely worse than spending 50 USD too much.
(2) Threshold and mode: ACTUAL at 100 % and MANUAL mode.
- Actual and not forecasted, for the same reason as in development: a wrong forecast must not block an environment the pipeline depends on.
- Manual and not automatic, and this is the key difference from development. In development the worst case is that Luis cannot bring up a test machine for a few hours: an inconvenience. In pre-production the worst case is that validation of an urgent deployment gets blocked — a security patch, say — and that is already an incident. Manual mode notifies, shows the proposed action and waits for Marta to approve it with a click, which preserves control without automating the risk.
(3) Three things that could go wrong and their mitigation:
| Risk | Mitigation |
|---|---|
| The action blocks the pipeline because the policy includes an action the deployment needs | Test the policy in development first through a complete deployment cycle; review the CloudTrail events of a real deployment to find out which actions are genuinely invoked |
TargetIds set wrongly and the SCP is applied to the whole OU, reaching production |
Always point at the specific account, never at the OU; deploy the action with the CDK and review the diff in the pull request; test with a policy that only denies a harmless action |
| Nobody knows how to remove it on a Sunday night | Runbook written in mercadofresco-infra/docs/runbooks/, with the exact organizations:DetachPolicy command, who has permission and what gets documented afterwards. And rehearse it once |
A fourth risk worth anticipating: the manual action alert may go unapproved if it arrives on a Friday afternoon. The mitigation is not technical but organisational: agreeing that pending manual actions are part of the on-call daily review, which is one of the operational excellence findings 11-01 left open.
Conclusion
MercadoFresco has gone from knowing what it spends to not being able to overshoot without finding out.
You can tell analysing from controlling: Cost Explorer looks backwards and needs someone to remember; Budgets looks forwards and needs nobody to do anything. And you can place them against anomaly detection, which answers a different question — "this does not look like your pattern" — and which does not catch a gradual 3 % monthly rise capable of blowing a budget in six months. Along with the five reasons the 10 USD alarm from 01-02 had become useless, the first of which is the gravest: an alert that is always red does active harm, because it teaches the team to ignore the whole channel.
You have the four budget types — cost, usage, Savings Plans and reservations — with the usage one as the least known and the one that catches what the cost budget cannot see when prices come down. And the periodicities, with the planned budget solving the real problem of the Christmas campaign: different amounts per month, from 2,000 in normal operation to 2,600 in December, so that the month needing most attention is not the very month when every alert gets ignored. With the most valuable side effect: putting a number on December forces you to estimate it, and estimating it forces you to talk to the business.
You have the distinction between thresholds on actual and forecasted cost, and the reason for configuring both: on the 9th with 51 USD spent, the actual threshold does not fire and the forecasted one does, leaving 21 days to correct course. With the two warnings about the forecast — it needs about five weeks of history and it extrapolates any one-off spend — and the three-threshold escalation with the rule that orders it: each threshold must have a different recipient and a different expected action, to the point where MercadoFresco's 50 % deliberately notifies nobody.
You have the ten specific budgets over the 1,749.60 USD bill: global at 2,000, production 1,400, pre-production 290, development 170, tooling 70, governance 55, plus the cross-cutting ones by the analitica tag (220), by the CloudWatch service (170), by NAT usage (900 GB) and the planned Christmas one. With the four design decisions: margins of 10 to 20 %, the sum of the specific ones below the global one so that the ceiling also warns, development as the only one with an action, and budgets that overlap on purpose because they watch different dimensions — and which for that reason are not added together.
You have creation from the console, the CLI and the CDK, which is how they are really maintained, with the three details that trip people up: the user:Key$Value format of the tag filter, which written wrongly creates a budget that filters down to zero and never warns you; IncludeTax and IncludeCredit at false so as to work on the service cost; and UseAmortized enabled from the start, so that the month a commitment is paid does not trip every threshold. Plus notifications over the three channels and the SNS topic policy with aws:SourceAccount and aws:SourceArn, which is the step most often forgotten.
And you have the part with teeth: budget actions, with their three types — IAM policy, SCP and stopping instances — and their two modes, governed by a rule with no convenient exceptions: automatic only where the worst case is an inconvenience; manual where the worst case is an incident. With the complete case of development account 333344445555, which freezes itself at 100 % of actual cost through an SCP that only denies creating expensive resources and touches nothing already running, pointed at the account and never at the OU, with a minimum-permission role and an unblocking runbook. And the real incident that justified all of it: an EKS cluster forgotten over a weekend, 40 USD instead of 220. With the warning to burn into memory: no restrictive automatic action in production, because a policy that prevents scaling on a campaign Friday turns a 300 USD saving into an outage that costs far more.
And you have the routine that sustains it all: the budget reports that open the meeting with a common document, the seven good practices, the four-step procedure for when a budget is exceeded for a legitimate reason — always starting with the unit cost, and always ending with a commit explaining the new amount — and the FinOps framework with its cycle of inform, optimise and operate, its three principles — teams own their spend, decisions are taken on value and not on cost, and the central team enables rather than controls — and the 30-minute monthly meeting that starts with the cost per order, produces a single action and in which nobody gets a surprise in public.
There is one lever left unused, and it is the only one that reduces the bill without changing anything at all in the architecture. Everything MercadoFresco has running — the Fargate tasks that are on 24 hours a day, the Lambda functions invoked every minute, the ElastiCache nodes that have not been switched off in months — is being paid for at on-demand price, that is, at the price paid by someone who might walk away tomorrow. Part of that consumption is completely predictable and will still be there a year from now. And AWS pays you for telling it so in advance.
In 11-05, "AWS Savings Plans", the optimisation cycle closes by committing capacity: the four purchase models with their discount and their risk, how the dollar-per-hour commitment works with a step-by-step numerical example, what each type covers and what it does not — important in an architecture like this one, mostly serverless — how to identify the stable baseline against the elastic part, and MercadoFresco's specific purchase plan with its monthly and annual saving. With the ordering that 11-03 already flagged and that is fully justified here: first switch off what is spare, then commit.
AWS Course
Module 1: Introduction to AWS
- What Is AWS?
- Setting Up Your AWS Account
- AWS Global Infrastructure
- The AWS Management Console
- AWS CLI and SDKs
Module 2: Core AWS Services
Module 3: Networking and Content Delivery
Module 4: Security and Identity
- AWS Identity and Access Management (IAM)
- AWS Key Management Service (KMS)
- Secrets Manager and Parameter Store
- AWS Shield
- AWS WAF
Module 5: Monitoring and Management
Module 6: Databases
Module 7: Application Integration
- Amazon SQS
- Amazon SNS
- Amazon EventBridge
- AWS Step Functions
- Integration Patterns: Idempotency, Retries and Dead-Letter Queues
