I Asked an LLM to Build AWS Infrastructure and It Tripled the Estimated Bill: Here's How I Added a Pre-Deploy Cost Check
LLMs love "production-grade" defaults like oversized instances and NAT Gateways that quietly inflate the estimated AWS bill. Here is how I add a pre-deploy cost check to AI-generated CloudFormation, so expensive templates get caught before they ever deploy.
The prompt that looks harmless
Imagine telling an AI infrastructure assistant:
"Set up a scalable web app with a database."
Seconds later, you get a valid CloudFormation template. It passes security checks and would deploy without errors. But look at what it would cost per month, and you may find a NAT Gateway, a Multi-AZ database, and oversized instances. Nobody asked for any of that.
Valid and safe does not mean affordable. That is the gap I wanted to close in my AI-powered AWS Deployment Assistant.
Why LLMs overspend
An LLM optimizes for "looks like a good production setup", not for your budget. It has seen countless tutorials where the architecture is highly available and over-provisioned, so it copies that pattern even when you only need a small demo.
Three common culprits:
- NAT Gateways: charged per hour plus per GB processed, even when idle.
- Multi-AZ databases: roughly double the database cost.
- Oversized instance types: the model picks "large" by default because it feels safe.
The idea: a cost gate before deployment
Instead of finding out on the monthly bill, I add one more layer to the pipeline:
1
Plain English → LLM → CloudFormation → Validation → COST CHECK → DeployIf the estimated cost is higher than the user's budget, the template never reaches deployment.
Step 1: Parse the template
First, extract every resource and its important properties (type, instance size, storage, Multi-AZ flag).
1
2
3
4
5
6
7
8
9
10
11
12
import json
def extract_resources(template_text):
template = json.loads(template_text)
resources = []
for name, res in template.get("Resources", {}).items():
resources.append({
"name": name,
"type": res["Type"],
"props": res.get("Properties", {}),
})
return resourcesI convert templates to JSON first. CloudFormation YAML uses short tags like
!Ref, which plain YAML parsers do not handle well.Step 2: Price each resource
Map each resource type to an estimated monthly cost. You can use the AWS Pricing API, or start with a simple lookup table for the services you support.
1
2
3
4
5
6
7
8
9
10
11
HOURS_PER_MONTH = 730
PRICE_PER_HOUR = {
"t3.micro": 0.0104,
"t3.medium": 0.0416,
"m5.large": 0.096,
}
def estimate_ec2(props):
hourly = PRICE_PER_HOUR.get(props.get("InstanceType"), 0.10)
return hourly * HOURS_PER_MONTHThese prices are approximate and vary by region, so always check the current AWS pricing page.
Step 3: Add a budget gate
1
2
3
4
5
6
7
8
9
10
11
def check_budget(total_cost, budget):
if total_cost <= budget:
return {"approved": True}
return {
"approved": False,
"feedback": (
f"Estimated ${total_cost:.0f}/month exceeds the "
f"${budget}/month budget. Reduce cost while keeping "
"the same functionality."
),
}Step 4: Let the LLM fix its own bill
When the budget check fails, the cost report goes back to the LLM as feedback. The model then replaces the expensive parts: smaller instances, a single-AZ database for dev, and no NAT Gateway where it is not needed.
In practice, one or two rounds of feedback are usually enough.
Before and after (example scenario)
Estimates based on AWS public on-demand pricing (us-east-1, 730 hrs/month). Actual cost varies by region and usage.
| Resource | First draft | After cost loop |
|---|---|---|
| EC2 | 2 x m5.large (~$140) | 2 x t3.medium (~$61) |
| Database | db.m5.large Multi-AZ (~$250) | db.t3.medium Single-AZ (~$50) |
| NAT Gateway | Yes (~$33 + data) | Removed ($0) |
| Load balancer | ALB (~$16) | ALB (~$16) |
| Estimated total | ~$439/month | ~$127/month |
Same functionality for a dev/demo workload, at roughly one third of the estimated cost.
Hidden costs people forget
- NAT Gateway hourly charge plus data processing
- Data transfer between Availability Zones and out to the internet
- Idle load balancers
- Unattached Elastic IPs and EBS volumes
An estimator that only counts instances will miss a big part of the bill.
Lessons learned
- Cost is a guardrail, just like security. Treat it as a hard check, not a suggestion.
- Give the LLM the budget upfront. Telling the model the constraint early reduces the number of retries.
- An estimate is not a quote. Be honest about approximations and show the user the assumptions.
- Feedback loops beat bigger prompts. Letting the model see its own cost report works better than a very long system prompt.
What's next
- Integrate AWS Cost Explorer to compare estimates with the real bill
- Add a "free-tier mode" that only allows free-tier eligible resources
- Show a per-resource cost breakdown in the UI
Final thoughts
AI can write infrastructure faster than any of us. But speed without cost awareness is just a faster way to get a surprise bill. A simple pre-deploy cost check turns an assistant from "it works" into "it works and I can afford it."
If you are building something similar, I would love to hear how you handle cost control. Drop it in the comments!
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article