The bill got approved. Nobody knows why it was that high.
Here is a scenario that happens more often than engineering teams admit.
The AWS bill comes in. Someone on the finance or DevOps side opens Cost Explorer, checks the total against last month, maybe groups by service. EC2 is up a bit - probably the new staging cluster. RDS looks about right. ECS is steady. Nothing is obviously wrong. The bill gets approved.
Three weeks later, someone notices that i-0d7f2a9 - a t3.xlarge somebody spun up for a proof of concept in February - has been running continuously since February. Not staging. Not production. A dev box. Billed the whole time.
Cost Explorer showed you the EC2 total. It did not show you that one engineer's forgotten instance was responsible for a meaningful slice of it.
This is not a tooling failure exactly. Cost Explorer is doing what it was built to do: aggregate billing data by service, region, account, tag. What it is not built to do is answer the question who is responsible for which resources, and are those resources actually being used right now?
Those are different questions, and they require different instrumentation.
What Cost Explorer actually gives you
Cost Explorer operates on billing records - the same data that underlies your invoice. It can slice by:
- Service (EC2, RDS, ECS, etc.)
- Region
- Account (if you use AWS Organizations)
- Tag (if your tagging is consistent - a big if)
- Usage type (instance-hours, GB-month, data transfer)
This is genuinely useful for trend analysis, budget forecasting, and catching large anomalies. If your bill jumps 40% month over month, Cost Explorer will tell you EC2 spend in us-east-1 roughly doubled, and you can start investigating from there.
What it cannot tell you:
- Which specific instance belongs to which engineer or team
- Whether a running instance is actively being worked on or sitting completely idle
- What fraction of paid uptime was actually used
The last point is the one that compounds quietly. An EC2 dev box that runs 24/7 but gets worked on for 6 hours a day has a utilization rate of 25%. The other 18 hours - nights, mornings before standup, weekends - are billed at the same rate as the 6 hours when someone was actually there. Cost Explorer shows you the full instance-hour cost. The idle portion is invisible.
Why tagging doesn't fully solve this
The standard recommendation is rigorous resource tagging: owner, env, team, cost-center. Enforce it with AWS Config rules. Build a tag compliance dashboard. Run aws ce get-cost-and-usage grouped by tag.
Tagging does improve attribution significantly. If your tagging discipline is excellent, you can answer "what did the platform team's dev environments cost last month?" fairly reliably.
But tagging still has two gaps that matter for this specific problem.
Gap one: tagging tells you ownership, not activity. A tag tells you owner=ali on i-0d7f2a9. It does not tell you that Ali finished her feature branch on Thursday, pushed it, and hasn't connected to that instance since. The instance keeps running. The tag keeps saying owner=ali. Cost Explorer keeps counting instance-hours and attributing them to Ali's team. Every one of those hours looks exactly like a productive work hour from the billing system's perspective.
Gap two: tagging discipline degrades under pressure. New instances get spun up during incidents, experiments, and deadline crunches. Tags get applied inconsistently or not at all. AWS Config rules catch violations after the fact; they don't prevent untagged resources from billing you while you wait for the compliance report. In practice, most organizations have a meaningful percentage of resources in (no tag) at any given time, and that percentage tends to cluster in dev and experimental workloads - exactly where the idle spend accumulates.
The idle-resource problem, stated plainly
Dev and staging resources follow a different usage pattern than production. Production runs continuous load - it needs to be up whenever users are active. Dev environments, staging clusters, QA databases: they run when an engineer is actively working in them. That might be 6 to 8 hours on a normal working day. It is close to zero on weekends, public holidays, and overnight.
A db.r6g.large RDS instance in your staging environment that runs 24/7 is paid for 168 hours a week. If your engineers work a standard schedule across a single timezone, it is likely being actively queried for somewhere in the range of 40 to 50 of those hours. The remaining 120-plus hours are idle - the instance is fully provisioned (allocated memory, reserved IOPS, storage throughput), but nothing is connecting to it.
EC2 dev boxes follow a similar pattern. ECS services running background workers for a staging environment sit idle after business hours. ASGs for non-production workloads hold their minimum capacity through nights and weekends even when no one is running tests against them.
This idle time is not always visible because the billing system cannot distinguish "running and being used" from "running and completely idle." Both states produce identical billing records: instance-hours consumed, charged at the on-demand or Reserved rate.
What you need that Cost Explorer doesn't provide
Solving this requires instrumentation at two layers that Cost Explorer doesn't touch.
Layer one: activity signal. You need a way to determine whether a resource is actually being used - not just "is it running" (CloudWatch can tell you that) but "is a human actively working with it right now?" Server-side metrics (CPU, network, connections) are a partial proxy but they lag and they miss a lot. A developer who is reading documentation and thinking about their next commit is still actively working. Their EC2 instance's CPU is at 0.2%. A connection-count check on their RDS shows one idle connection held open by their ORM. Every server-side heuristic says "this is idle" when a person is actually mid-session.
A more reliable signal comes from the client side: is the engineer's machine active, is their work-related tooling in focus, are they doing something that means this environment should stay up? This is what activity-driven resource management refers to - using the actual presence and work state of the human to determine whether their cloud resources should be running, rather than inferring it from server-side metrics that can't see the human at all.
Layer two: per-resource attribution with idle cost visibility. Even if you solve the activity signal problem, you need somewhere to surface the cost consequence. "Ali's staging RDS ran for 63 hours this weekend and Ali was not working" needs to be a visible line item - not buried in an EC2 total, not aggregated into a team cost center, but explicitly visible as idle spend attributed to a specific resource and owner. That visibility is what turns the problem from an invisible background cost into something engineering managers and FinOps leads can actually act on.
How this maps to specific resource types
The idle-cost problem distributes unevenly across resource types. A few patterns worth knowing:
EC2 dev boxes are the highest-visibility case. A developer's personal dev instance runs when they run. When they log off, open their laptop the next morning to check email and then go to a meeting, commute home, take a day off - the instance doesn't know. It keeps running.
RDS staging databases are the most consistent idle accumulators. Databases don't scale down with load the way compute can. A db.r6g.xlarge staging RDS holds its full provisioned capacity whether it's handling 500 queries per second or none. And because stopping an RDS instance requires it to restart cold (with a potential 7-day auto-restart on stopped instances in AWS), teams often leave staging databases running continuously rather than dealing with the operational friction of stopping and starting them.
ECS services (non-production) tend to idle at their minimum task count. If you have a staging backend service with a minimum of 2 tasks, those 2 tasks run continuously even when no staging traffic exists - which is most of every weekend for most teams.
ASGs in staging and QA hold their minimum capacity configuration regardless of actual load. If your staging ASG minimum is 2 instances, you are paying for 2 instances at 3am on Sunday morning. There may be a strong case that minimum should be 0 outside working hours, but unless something is actively managing that floor, it stays at 2.
The attribution gap in practice
The consequence of all this is that your cloud cost review process is operating on incomplete information. You can see total spend. You can see spend by service. With good tagging, you can see spend by team. What you typically cannot see is:
- What fraction of that team's spend was on actively-used resources vs. idle resources
- Which specific environments drove the idle cost
- Whether a particular engineer's dev setup is systematically over-provisioned or left running through weekends
Without that visibility, cost optimization becomes a blunt instrument: resize everything, enforce strict schedules, mandate that engineers stop their instances when they go home. Schedules create their own failure modes (they don't know about your mid-deploy, your late incident, your timezone). Size constraints affect production and dev alike. And manual instance-stop policies rely on engineers remembering - which they don't, consistently, under deadline pressure.
The more precise approach is to make idle cost visible at the resource level, with ownership attribution, so that optimization decisions are based on actual usage patterns rather than assumptions about when work happens.
Starting points
If you want to get a clearer picture of where idle cost is accumulating in your AWS environment before you instrument anything:
Pull instance uptime vs. CloudWatch activity. For each EC2 dev instance, compare hours running (from billing) against hours with CPU > 5% (from CloudWatch metrics). The gap is your idle floor - an underestimate, because low-CPU doesn't mean idle, but a useful starting point.
Check RDS connection counts over the weekend. For staging RDS instances, pull
DatabaseConnectionsfor the previous Saturday and Sunday. How many hours had zero connections? That's provisioned capacity you paid for with no active user.Review ECS minimum task counts for non-production clusters. Pull your ECS service configurations and flag any staging or QA service with a non-zero minimum. Each of those task minimums runs continuously.
Run a tag compliance audit on running instances. Use
aws ec2 describe-instancesfiltered to running state and check what percentage lack anownerorteamtag. Untagged resources in dev accounts are usually ungoverned idle spend.
The point of this exercise isn't to build a manual optimization workflow - it's to make the problem concrete before you decide how to address it systematically.
If you're working on this problem - attribution of idle cost to specific engineers, teams, or environments, with activity-aware automation rather than fixed schedules - Trigops is built around exactly this use case. It's worth a look: trigops.com.
Top comments (0)