<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Trigops</title>
    <description>The latest articles on DEV Community by Trigops (trigops).</description>
    <link>https://dev.to/trigops</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F13450%2Feff5596c-0fe3-4be8-b483-06b0deb1eb94.png</url>
      <title>DEV Community: Trigops</title>
      <link>https://dev.to/trigops</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/trigops"/>
    <language>en</language>
    <item>
      <title>You Fixed Idle EC2. Now Which Team Is Still Running a Staging RDS Nobody Owns?</title>
      <dc:creator>Trigops</dc:creator>
      <pubDate>Mon, 06 Jul 2026 06:23:08 +0000</pubDate>
      <link>https://dev.to/trigops/you-fixed-idle-ec2-now-which-team-is-still-running-a-staging-rds-nobody-owns-3nc3</link>
      <guid>https://dev.to/trigops/you-fixed-idle-ec2-now-which-team-is-still-running-a-staging-rds-nobody-owns-3nc3</guid>
      <description>&lt;h2&gt;
  
  
  The database that outlived the feature branch
&lt;/h2&gt;

&lt;p&gt;Six weeks ago your team provisioned a &lt;code&gt;db.t3.medium&lt;/code&gt; RDS instance for a feature branch - call it &lt;code&gt;feature/payments-v2&lt;/code&gt;. The branch shipped, got merged, went to production. The RDS instance did not follow. It sat there, a snapshot of a decision nobody reversed, accruing roughly $0.068 per hour around the clock.&lt;/p&gt;

&lt;p&gt;This is not a hypothetical. It is the structural default of how AWS resources get created versus how they get cleaned up. Compute (EC2, ECS tasks) tends to get attention first because the cost is visible and the ownership is obvious. Databases and services that support work-in-progress are harder to kill: someone worries the data is still needed, the cleanup ticket never gets prioritized, and the resource keeps billing.&lt;/p&gt;

&lt;p&gt;The gap between "we shipped" and "we cleaned up" is where a disproportionate share of dev and staging spend lives.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why EC2 gets fixed first - and what gets left behind
&lt;/h2&gt;

&lt;p&gt;EC2 idle cost is the easiest problem to see. One engineer, one dev box, $0.17/hour on a &lt;code&gt;t3.xlarge&lt;/code&gt;, running through 16 hours of nights and weekends every day. The math is a single multiplication. Tooling exists; the problem gets addressed.&lt;/p&gt;

&lt;p&gt;But EC2 is rarely the only resource a modern engineering workflow touches. By the time a feature reaches staging, a typical setup has also spun up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;RDS&lt;/strong&gt; - a database instance dedicated to that environment, often sized for load testing rather than steady-state&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ECS services&lt;/strong&gt; - one or more containerized services running against that database&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An ASG behind a load balancer&lt;/strong&gt; - for anything that needed to validate autoscaling behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When the engineer closes their laptop, the EC2 dev box is the thing everyone thinks about. The RDS instance, the ECS service, and the ASG are still running. They are running because nothing in the default AWS lifecycle stops them, and because the mental model of "I'm done" applies to the compute layer, not to the data and service layers.&lt;/p&gt;




&lt;h2&gt;
  
  
  The ownership problem at the team level
&lt;/h2&gt;

&lt;p&gt;The resource hierarchy here matters. An EC2 instance has a clear owner: the engineer who launched it. But a staging RDS instance often has a &lt;em&gt;team&lt;/em&gt; as its nominal owner, which in practice means nobody in particular.&lt;/p&gt;

&lt;p&gt;The canonical failure mode:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Engineer A provisions &lt;code&gt;rds-staging-payments&lt;/code&gt; to support a sprint's worth of work.&lt;/li&gt;
&lt;li&gt;Sprint ends. Engineer A moves on.&lt;/li&gt;
&lt;li&gt;Engineer B runs some tests against it two weeks later. It is now load-bearing for B's work.&lt;/li&gt;
&lt;li&gt;Nobody owns the cleanup. Nobody wants to delete something someone else might be using.&lt;/li&gt;
&lt;li&gt;Three months pass. The instance is still running. Cost has been accruing the entire time.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The same pattern plays out with ECS services. A long-running containerized service that was stood up for integration testing stays alive because the service definition exists, the task count is nonzero, and no automated policy has ever said otherwise.&lt;/p&gt;

&lt;p&gt;This is not a tooling failure in the narrow sense. It is an incentives and visibility failure: the cost is distributed across the bill, the ownership is diffuse, and the effort required to clean up is slightly larger than the effort required to ignore it.&lt;/p&gt;




&lt;h2&gt;
  
  
  What activity-driven resource management actually means for these cases
&lt;/h2&gt;

&lt;p&gt;Activity-driven means exactly what it says: resources run when people are actively working with them, and pause when they are not. It is worth being precise about what "actively working" means, because it is not the same as "the server is receiving requests" (which is what server-side metrics measure) and it is not the same as "it is 9am on a Tuesday" (which is what a schedule measures).&lt;/p&gt;

&lt;p&gt;Activity-driven means the tooling detects real user presence on the engineer's machine - whether their terminal is connected to the resource, whether relevant work tools are in focus - and makes pause/resume decisions based on that signal. The resource is up when it is needed and paused when it is not.&lt;/p&gt;

&lt;p&gt;For a multi-resource environment - EC2 + RDS + ECS service - this changes the problem in two ways:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First, the whole environment pauses together.&lt;/strong&gt; When the engineer's activity drops off (end of day, lunch, a meeting that runs long), the EC2 instance pauses. But the RDS and ECS service that support that same environment should pause with it. Activity-driven orchestration applied at the environment level means the database is not billing overnight simply because nobody explicitly stopped it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second, multi-account visibility surfaces the orphans.&lt;/strong&gt; The hardest resource to clean up is the one nobody knows exists. A team-level dashboard that shows every RDS instance, ECS service, and ASG across all accounts - with last-activity timestamps - turns invisible cost into addressable cost. The &lt;code&gt;rds-staging-payments&lt;/code&gt; instance that has not had a meaningful connection in 47 days becomes visible. The decision to snapshot-and-delete it becomes a five-minute task instead of an investigation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Walking the resource types
&lt;/h2&gt;

&lt;h3&gt;
  
  
  EC2
&lt;/h3&gt;

&lt;p&gt;The compute layer is where most engineers start. The dev box is the canonical example: a &lt;code&gt;t3.xlarge&lt;/code&gt; or similar, used for 8-9 hours of active work and billed for all 24. Activity-driven pause/resume on EC2 is well-understood and the ROI arithmetic is straightforward.&lt;/p&gt;

&lt;p&gt;The less-discussed case is the EC2 instance that is part of a staging &lt;em&gt;environment&lt;/em&gt; - provisioned not as a personal dev box but as the application tier of a feature branch. When the branch ships, this instance should be a cleanup target. Without visibility into when it was last actively used, it just keeps running.&lt;/p&gt;

&lt;h3&gt;
  
  
  RDS
&lt;/h3&gt;

&lt;p&gt;RDS idle cost compounds differently from EC2. You cannot stop an RDS instance indefinitely - AWS automatically restarts instances that have been stopped for more than 7 days. This means that without automation, a "stopped" RDS instance will restart itself and resume billing, often without anyone noticing.&lt;/p&gt;

&lt;p&gt;The cost profile of a running RDS instance also does not respond to inactivity the way EC2 does. EC2 at least has the option to stop and incur only storage costs. RDS in Multi-AZ runs two instances continuously regardless of whether anyone has issued a query in two weeks.&lt;/p&gt;

&lt;p&gt;The right response for genuinely idle staging RDS is often snapshot-and-delete rather than pause - especially for feature-branch databases where the data has no long-term value. The barrier is visibility: you need to know which instances are idle and which are load-bearing before you can act.&lt;/p&gt;

&lt;h3&gt;
  
  
  ECS
&lt;/h3&gt;

&lt;p&gt;ECS services are the most invisible cost in a typical staging setup. Task costs scale with the number of running tasks and their CPU/memory allocation. A service running two tasks of &lt;code&gt;0.5 vCPU / 1GB&lt;/code&gt; looks small on the bill individually but aggregates quickly across a staging environment with five or six services.&lt;/p&gt;

&lt;p&gt;More importantly, ECS service state is persistent by default. The desired task count does not change because the team stopped working on that service. If you stood up a service for load testing and set desired count to 2, it is still running 2 tasks until someone explicitly changes that.&lt;/p&gt;

&lt;p&gt;Activity-driven management of ECS means tracking which services are connected to active development work and scaling desired count to 0 during idle periods, then back up when work resumes. The challenge is that ECS services often lack clear individual ownership - they belong to the team's staging environment, not to a specific engineer.&lt;/p&gt;

&lt;h3&gt;
  
  
  ASGs
&lt;/h3&gt;

&lt;p&gt;Autoscaling Groups are the most architecturally complex case. An ASG manages instance count dynamically, which means naive pause logic can interact badly with scaling policies. The right approach is to set minimum capacity to 0 during known-idle periods rather than stopping instances individually.&lt;/p&gt;

&lt;p&gt;For staging ASGs, the pattern is: set desired and minimum to 0 at end of day / start of weekend, restore to working values when development activity resumes. This requires coordination with the load balancer health checks and any warmup configuration to avoid false alarms when capacity comes back.&lt;/p&gt;




&lt;h2&gt;
  
  
  The governance layer: who can pause what
&lt;/h2&gt;

&lt;p&gt;For a solo developer managing their own dev box, governance is simple - it is their resource. For a team of eight engineers sharing a staging environment with shared RDS and ECS infrastructure, it gets more complex.&lt;/p&gt;

&lt;p&gt;The questions that need answers before you automate anything:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can one engineer's inactivity pause an RDS instance another engineer is actively querying?&lt;/li&gt;
&lt;li&gt;Who has override authority to keep a resource running through a scheduled pause window?&lt;/li&gt;
&lt;li&gt;How does the system behave when engineer A's session is active but engineer B's is not?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The answers depend on your team's workflow, but the pattern that tends to work is: personal resources (dev boxes, feature-branch instances) follow individual activity; shared resources follow team-level activity with a quorum or last-active-engineer model. Shared resources also need explicit override capability - the ability to mark a resource "keep running" for a defined period without disabling automation entirely.&lt;/p&gt;

&lt;p&gt;RBAC and ABAC controls at the resource level matter here. The ability to share governance policies across an organization, apply them at the account level, and audit who changed what is what distinguishes an actual governance solution from a cron job that stops instances at night.&lt;/p&gt;




&lt;h2&gt;
  
  
  Starting the audit
&lt;/h2&gt;

&lt;p&gt;If you want to understand the scope of the problem before investing in a full solution, the AWS CLI gives you a reasonable starting point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Find RDS instances not accessed recently (CloudWatch):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# List all RDS instances and their status&lt;/span&gt;
aws rds describe-db-instances &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'DBInstances[*].{ID:DBInstanceIdentifier,Class:DBInstanceClass,Status:DBInstanceStatus,MultiAZ:MultiAZ}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check DatabaseConnections metric for a specific instance (last 7 days)&lt;/span&gt;
aws cloudwatch get-metric-statistics &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--namespace&lt;/span&gt; AWS/RDS &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--metric-name&lt;/span&gt; DatabaseConnections &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--dimensions&lt;/span&gt; &lt;span class="nv"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;DBInstanceIdentifier,Value&lt;span class="o"&gt;=&lt;/span&gt;rds-staging-payments &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--start-time&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'7 days ago'&lt;/span&gt; +%Y-%m-%dT%H:%M:%SZ&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--end-time&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; +%Y-%m-%dT%H:%M:%SZ&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--period&lt;/span&gt; 86400 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--statistics&lt;/span&gt; Maximum &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Datapoints[*].{Time:Timestamp,MaxConnections:Maximum}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Find ECS services with no recent task activity:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# List ECS services across a cluster with running task counts&lt;/span&gt;
aws ecs list-services &lt;span class="nt"&gt;--cluster&lt;/span&gt; staging-cluster &lt;span class="nt"&gt;--output&lt;/span&gt; text | &lt;span class="se"&gt;\&lt;/span&gt;
  xargs &lt;span class="nt"&gt;-I&lt;/span&gt;&lt;span class="o"&gt;{}&lt;/span&gt; aws ecs describe-services &lt;span class="nt"&gt;--cluster&lt;/span&gt; staging-cluster &lt;span class="nt"&gt;--services&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'services[*].{Name:serviceName,Desired:desiredCount,Running:runningCount,LastDeploy:deployments[0].updatedAt}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Find ASGs with nonzero minimum capacity and no recent scaling activity:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;awsaws autoscaling describe-auto-scaling-groups &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'AutoScalingGroups[?MinSize &amp;gt; `0`].{Name:AutoScalingGroupName,Min:MinSize,Desired:DesiredCapacity,Max:MaxSize}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These queries give you a snapshot. They do not tell you &lt;em&gt;why&lt;/em&gt; resources exist or who owns them - that context lives in your team's memory, which is exactly the problem. Tags help if your team uses them consistently; most staging environments have inconsistent tagging.&lt;/p&gt;




&lt;h2&gt;
  
  
  The shape of the actual problem
&lt;/h2&gt;

&lt;p&gt;Idle EC2 is visible and well-solved. The harder problem is the layer beneath it: databases that outlive their feature branches, ECS services running at nonzero task count for work that shipped three sprints ago, ASGs holding minimum capacity for a load test that concluded weeks ago.&lt;/p&gt;

&lt;p&gt;The common thread is not technical complexity - pausing an RDS instance or scaling an ECS service to zero is not hard. The common thread is visibility and ownership. Resources that nobody is actively watching, that belong to a team rather than a person, that were provisioned for a specific purpose that has since been achieved - those are the resources that keep billing quietly.&lt;/p&gt;

&lt;p&gt;The audit query above is a reasonable first step. The durable fix is a system that surfaces idle resources continuously, ties them to the human activity that justifies running them, and makes the cost of inactivity visible at the team level rather than buried in the monthly bill.&lt;/p&gt;




&lt;p&gt;If you are working through this for your own team's AWS accounts, &lt;a href="https://trigops.com" rel="noopener noreferrer"&gt;Trigops&lt;/a&gt; is built specifically for this: activity-driven pause and resume across EC2, RDS, ECS, and ASGs, with multi-account visibility and team-level governance. Early Adopter pricing + VAT. Worth a look if the audit above surfaces more than you expected.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>devops</category>
      <category>finops</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Multi-Account AWS Cost Visibility Without a FinOps Team</title>
      <dc:creator>Trigops</dc:creator>
      <pubDate>Mon, 29 Jun 2026 06:07:41 +0000</pubDate>
      <link>https://dev.to/trigops/multi-account-aws-cost-visibility-without-a-finops-team-3788</link>
      <guid>https://dev.to/trigops/multi-account-aws-cost-visibility-without-a-finops-team-3788</guid>
      <description>&lt;h2&gt;
  
  
  The Spreadsheet That Lives in Someone's Head
&lt;/h2&gt;

&lt;p&gt;You manage AWS cost for an engineering org with eight accounts: production, per-team staging, a data-science sandbox, a legacy account from the acquisition two years ago that still runs a handful of RDS instances nobody has formally decommissioned. Cost Explorer exists. You open it, switch to the org-level view, and see a number. A large number. It does not tell you whether that number is reasonable, which team owns the spike in us-east-1 last Tuesday, or whether the &lt;code&gt;r6g.2xlarge&lt;/code&gt; RDS cluster in the staging account ran all weekend because someone forgot to stop it or because there was actually a load test.&lt;/p&gt;

&lt;p&gt;The practical answer for most engineering orgs at this stage is: somebody builds a spreadsheet. It gets stale. Then the cost review meeting is an hour of archaeology.&lt;/p&gt;

&lt;p&gt;This article is about building something better - not a platform, not a FinOps tool purchase, but a clear architectural picture of where AWS multi-account cost visibility actually breaks down and what the minimal viable fix looks like. We'll ground it in the resource types that generate the most surprise at the end of the month: EC2, ECS, RDS, and ASG.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Cost Explorer Alone Isn't Enough
&lt;/h2&gt;

&lt;p&gt;Cost Explorer is good at billing history. It is less good at answering operational questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which EC2 instances are running right now and are they doing anything?&lt;/li&gt;
&lt;li&gt;How long has this ECS service been at zero requests per minute?&lt;/li&gt;
&lt;li&gt;Who owns this RDS cluster and does that team know it ran all weekend?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The gap is that Cost Explorer is a billing ledger. Billing accrues by the hour regardless of whether a resource is serving traffic, running tests, or simply existing because no one turned it off. For production, that's fine - production should run. For dev and staging, the relationship between uptime and actual utility is much weaker.&lt;/p&gt;

&lt;p&gt;In a single-account org you can build a reasonable picture with Cost Allocation Tags and a few Athena queries over the CUR (Cost and Usage Report). Multi-account orgs hit two compounding problems:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Tag discipline is uneven across accounts.&lt;/strong&gt; The production account has a mature tagging policy enforced by SCPs. The sandbox account the data team set up eighteen months ago has almost nothing tagged, and the data team has since reorganized. Untagged spend is a black hole in Cost Explorer's group-by views.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Utilization is invisible in billing data.&lt;/strong&gt; A stopped EC2 instance still shows an EBS charge. A running RDS instance in a staging account at 1% CPU looks identical in the billing line to a running RDS instance serving real traffic. The bill doesn't carry signal about whether the resource was actively used.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Architecture: Three Data Planes
&lt;/h2&gt;

&lt;p&gt;Building multi-account visibility that actually answers operational questions requires stitching together three separate data planes:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Billing data (what you're paying)
&lt;/h3&gt;

&lt;p&gt;The Cost and Usage Report is the authoritative source for what AWS charged you. Enable CUR at the org management account level with hourly granularity and resource-level line items. Land it in S3, crawl it with a Glue crawler, query it with Athena.&lt;/p&gt;

&lt;p&gt;The key table relationship: &lt;code&gt;line_item_resource_id&lt;/code&gt; is the ARN of the resource that generated a charge. This is your join key to the other two data planes.&lt;/p&gt;

&lt;p&gt;Useful starting queries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Top spenders by account and service, last 30 days&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;line_item_usage_account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;line_item_product_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line_item_unblended_cost&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_cost&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;cur_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cur_table&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;line_item_line_item_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Usage'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;month&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DATE_TRUNC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'month'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;CURRENT_DATE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Untagged EC2 spend by account (tag enforcement gap finder)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;line_item_usage_account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line_item_unblended_cost&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;untagged_cost&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;cur_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cur_table&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;line_item_product_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'AmazonEC2'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;resource_tags_user_team&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;month&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DATE_TRUNC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'month'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;CURRENT_DATE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Inventory data (what's running)
&lt;/h3&gt;

&lt;p&gt;Billing data tells you what you paid. It doesn't tell you the current state of the resource or who's responsible for it. For that you need an inventory pass across accounts.&lt;/p&gt;

&lt;p&gt;The standard approach is an AWS Config aggregator at the org level. Enable it from the management account; it federates resource configuration snapshots from all member accounts into a single query interface.&lt;/p&gt;

&lt;p&gt;For the resource types that matter most:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;EC2:&lt;/strong&gt; &lt;code&gt;aws:ec2:instance&lt;/code&gt; - state (running/stopped), instance type, launch time, attached tags&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RDS:&lt;/strong&gt; &lt;code&gt;aws:rds:dbinstance&lt;/code&gt; - status (available/stopped), instance class, MultiAZ, last modified&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ECS:&lt;/strong&gt; &lt;code&gt;aws:ecs:service&lt;/code&gt; - desiredCount, runningCount, lastDeployment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ASG:&lt;/strong&gt; &lt;code&gt;aws:autoscaling:autoScalingGroup&lt;/code&gt; - min/max/desired, instance count&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can query this with AWS Config Advanced Queries (which speak a subset of SQL):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- All running EC2 instances across org, with tags&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;awsRegion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;resourceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;instanceType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;tags&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;aws_config_configuration_snapshot&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;resourceType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'AWS::EC2::Instance'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'running'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For ECS services the useful signal is &lt;code&gt;runningCount&lt;/code&gt; vs &lt;code&gt;desiredCount&lt;/code&gt;. A service sitting at &lt;code&gt;desired=1, running=1&lt;/code&gt; but with zero CloudWatch &lt;code&gt;RequestCount&lt;/code&gt; metrics for 72 hours is a candidate for investigation - it may be legitimate (a background worker) or it may be forgotten.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Activity data (what's being used)
&lt;/h3&gt;

&lt;p&gt;This is the hardest layer to build and the most valuable. Billing tells you uptime cost; inventory tells you what exists; activity tells you whether any human or system is actually using a resource right now.&lt;/p&gt;

&lt;p&gt;For EC2 and ECS, CloudWatch &lt;code&gt;CPUUtilization&lt;/code&gt; is the proxy most teams reach for first. It is better than nothing but has known blind spots:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A dev box running at 0.2% CPU might have a developer actively connected over SSH writing code in a terminal - CPU is low because they're thinking, not because the machine is idle.&lt;/li&gt;
&lt;li&gt;An ECS service at 0% CPU might be perfectly legitimately idle, waiting for an event that happens once a day.&lt;/li&gt;
&lt;li&gt;Conversely, a forgotten RDS instance might spike to 30% CPU periodically from an automated job that nobody thought to cancel when the project ended.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CPU utilization is a workload signal. It's not a human-presence signal. For development and staging resources, what you actually want to know is: &lt;strong&gt;is a person actively working on this right now?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's a meaningfully different question. This is what &lt;em&gt;activity-driven&lt;/em&gt; resource management attempts to answer - tracking real user presence and work-tool focus at the desktop level, rather than inferring activity from server-side metrics. Instead of asking "is the CPU busy?", an activity-driven system asks "is the engineer who owns this box actually at their desk, with their IDE open?" The two questions have different answers surprisingly often.&lt;/p&gt;

&lt;p&gt;For the purposes of a visibility architecture, whether you're building activity detection yourself or relying on a tool that provides it, the key architectural point is that &lt;strong&gt;server-side metrics and billing data alone cannot tell you whether dev/staging resource usage is intentional.&lt;/strong&gt; You need a signal from the demand side, not just the supply side.&lt;/p&gt;




&lt;h2&gt;
  
  
  Stitching It Together: The Minimal Viable Dashboard
&lt;/h2&gt;

&lt;p&gt;For an org without a dedicated FinOps team, the goal is a weekly operational artifact that answers four questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What did we spend per account and per team last week?&lt;/li&gt;
&lt;li&gt;Which resources ran longest with no detectable activity?&lt;/li&gt;
&lt;li&gt;Which accounts have the worst untagged spend?&lt;/li&gt;
&lt;li&gt;What's the week-over-week trend?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You don't need a commercial platform to answer these. A reasonable minimal stack:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CUR → S3 → Athena&lt;/strong&gt; for billing queries. Schedule a weekly Athena query via EventBridge + Lambda and write results to another S3 prefix.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Config Aggregator → Advanced Queries&lt;/strong&gt; for inventory snapshots. The same Lambda can call &lt;code&gt;select_aggregate_resource_config&lt;/code&gt; via the SDK.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CloudWatch Metrics Insights&lt;/strong&gt; for utilization data. A cross-account metrics query (available in CloudWatch Metrics Insights with a metrics stream or via the &lt;code&gt;AWS/EC2&lt;/code&gt; namespace with cross-account observability enabled) can pull &lt;code&gt;CPUUtilization&lt;/code&gt; P50 over the last 7 days for all instances.&lt;/p&gt;

&lt;p&gt;Join on ARN / resource ID. Export to a simple HTML report or push to a Slack channel. The output doesn't need to be beautiful; it needs to be accurate and regular.&lt;/p&gt;

&lt;p&gt;A skeleton Lambda handler for the billing aggregation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;

&lt;span class="n"&gt;ATHENA_DB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cur_db&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;ATHENA_OUTPUT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;s3://your-bucket/athena-results/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;WORKGROUP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;primary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;QUERY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
SELECT
  line_item_usage_account_id AS account,
  resource_tags_user_team AS team,
  ROUND(SUM(line_item_unblended_cost), 2) AS week_cost_usd
FROM cur_db.cur_table
WHERE line_item_line_item_type = &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Usage&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;
  AND line_item_usage_start_date &amp;gt;= DATE_ADD(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;day&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, -7, CURRENT_DATE)
GROUP BY 1, 2
ORDER BY 3 DESC
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;athena&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;us-east-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start_query_execution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;QueryString&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;QUERY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;QueryExecutionContext&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Database&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ATHENA_DB&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;ResultConfiguration&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OutputLocation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ATHENA_OUTPUT&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;WorkGroup&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;WORKGROUP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;QueryExecutionId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;QueryExecutionId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run this in the management account with a cross-account IAM role that has &lt;code&gt;athena:StartQueryExecution&lt;/code&gt; and &lt;code&gt;s3:PutObject&lt;/code&gt;. The CUR data is already org-wide if you set it up at the management account level.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Tag Governance Problem
&lt;/h2&gt;

&lt;p&gt;None of the above is useful if half your spend is untagged. Multi-account tag governance is its own discipline, but the minimum viable practice:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SCPs on create actions.&lt;/strong&gt; A Service Control Policy that denies &lt;code&gt;ec2:RunInstances&lt;/code&gt;, &lt;code&gt;rds:CreateDBInstance&lt;/code&gt;, and &lt;code&gt;ecs:CreateService&lt;/code&gt; when the required tags (&lt;code&gt;Team&lt;/code&gt;, &lt;code&gt;Environment&lt;/code&gt;, &lt;code&gt;Owner&lt;/code&gt;) are missing prevents untagged resources from being created. Attach it to the OU containing non-production accounts first; production teams will complain less if you start there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Config Rules for drift detection.&lt;/strong&gt; &lt;code&gt;required-tags&lt;/code&gt; is a managed Config rule. Combined with the aggregator, it gives you a cross-account view of resources that exist but are missing tags - often legacy resources predating the policy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A remediation queue, not a penalty.&lt;/strong&gt; The goal is visibility, not compliance theater. Tag violations should generate a ticket in your engineering team's backlog, not an automated termination (until you have very high confidence in the policy). Terminating a production resource because someone forgot a tag is a worse outcome than the tag missing.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Won't Solve
&lt;/h2&gt;

&lt;p&gt;A billing + inventory + utilization dashboard tells you that your staging RDS ran all weekend and cost $47. It does not tell you why, and it does not prevent it next weekend.&lt;/p&gt;

&lt;p&gt;Prevention requires either a schedule (turn it off at 6 PM Friday, turn it on Monday morning - fragile if the team works irregular hours) or something that responds to actual human activity. A schedule can be built in an afternoon with RDS maintenance windows or AWS Instance Scheduler. It will start misfiring as soon as the engineering team's actual work pattern diverges from the schedule - which is immediately, for most teams.&lt;/p&gt;

&lt;p&gt;The harder architectural question is: what signal should drive resource state? Scheduled stop/start is a blunt instrument. Utilization-based autoscaling handles load variation but not the fundamental question of whether anyone is present and working. Activity-driven approaches - where resource state follows the engineer's actual desktop session, IDE focus, and work-tool activity - are a newer category, purpose-built for this gap in dev/test environments.&lt;/p&gt;




&lt;h2&gt;
  
  
  Starting Points
&lt;/h2&gt;

&lt;p&gt;If you're building this from scratch, the priority order that gives the most signal per hour of work:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Enable CUR with resource-level line items&lt;/strong&gt; at the org level. Everything else in cost visibility builds on this. Without resource-level data, the billing plane is opaque.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enable Config Aggregator.&lt;/strong&gt; Inventory without billing is guesswork; billing without inventory is a black box. Together they let you ask "what is this charge and what resource caused it?"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build the tag gap report first.&lt;/strong&gt; Before you can reason about team-level spend, you need to know how much of your spend is attributable. Fix the gaps with SCPs on new resources; chase legacy untagged resources with Config remediation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add utilization signals last.&lt;/strong&gt; CloudWatch Metrics Insights cross-account is the right tool once you have billing and inventory aligned. CPU P50 over 7 days by resource is a reasonable first cut at identifying idle candidates.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The staging RDS that ran all weekend is a $47 problem once. It is a $2,400 problem annually per forgotten instance. Across eight accounts with mixed tag hygiene and teams that move fast, the aggregate is significant - not because any single resource is catastrophically wasteful, but because the pattern repeats undetected at scale.&lt;/p&gt;

&lt;p&gt;Visibility is the prerequisite to action. Get the data plane right first.&lt;/p&gt;




&lt;p&gt;If this is a problem you're actively working on - staging and dev resources running past their useful window, or cost visibility spread thin across accounts - &lt;a href="https://trigops.com" rel="noopener noreferrer"&gt;Trigops&lt;/a&gt; is built specifically for this: activity-driven pause and resume for EC2, ECS, RDS, and ASG, with per-account and per-team visibility. Worth a look if you want the detection and the action layer in one place.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>devops</category>
      <category>finops</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Why Your Dev Environment Keeps Billing You After Hours (And How to Actually Stop It)</title>
      <dc:creator>Trigops</dc:creator>
      <pubDate>Wed, 10 Jun 2026 08:09:00 +0000</pubDate>
      <link>https://dev.to/trigops/why-your-dev-environment-keeps-billing-you-after-hours-and-how-to-actually-stop-it-22l0</link>
      <guid>https://dev.to/trigops/why-your-dev-environment-keeps-billing-you-after-hours-and-how-to-actually-stop-it-22l0</guid>
      <description>&lt;h2&gt;
  
  
  The weekend bill nobody asked for
&lt;/h2&gt;

&lt;p&gt;It's Monday morning. Your team's AWS bill arrived Friday night. You skim it over coffee.&lt;/p&gt;

&lt;p&gt;Staging RDS: running all weekend. Eight EC2 dev boxes: all on. Three ECS services your team uses for integration testing: all up. Nobody was in the office Saturday. One engineer pushed a single commit Sunday afternoon, then closed their laptop.&lt;/p&gt;

&lt;p&gt;You're paying for 72 hours of "available" across 12 resources. The actual engineering work that happened over those 72 hours: maybe four hours, on one machine.&lt;/p&gt;

&lt;p&gt;This isn't a spending problem. It's a &lt;em&gt;visibility&lt;/em&gt; problem — and a structural one in how cloud resources are traditionally managed.&lt;/p&gt;




&lt;h2&gt;
  
  
  What you're actually paying for
&lt;/h2&gt;

&lt;p&gt;Let's put concrete numbers to it. Consider a team of six engineers, each with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One &lt;code&gt;t3.medium&lt;/code&gt; EC2 dev instance (~$0.0416/hr on-demand, us-east-1)&lt;/li&gt;
&lt;li&gt;One shared &lt;code&gt;db.t3.medium&lt;/code&gt; RDS staging instance (~$0.068/hr)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Running 24/7/365, that's:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;6 × t3.medium EC2:  6 × $0.0416 × 8,760 = ~$2,186/yr
1 × db.t3.medium RDS:      $0.068 × 8,760 = ~$596/yr
Total (rough):                               ~$2,782/yr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now ask how many of those 8,760 hours the resources are actively used. Even generous assumptions — engineers working 8-hour focused days, 5 days a week — give you roughly 2,080 hours a year. The remaining 6,680 hours are nights, weekends, vacations, public holidays, all-hands weeks, and the long stretches between a developer closing their laptop at 6pm and opening it again at 9am.&lt;/p&gt;

&lt;p&gt;That's a large share of uptime that produces zero engineering output. The resources sit idle and the meter keeps running.&lt;/p&gt;

&lt;p&gt;The problem scales with headcount. A team of 15 engineers with comparable per-engineer infrastructure is paying for that same pattern across every account and region they touch.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why the obvious fixes don't work
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Fixed schedules
&lt;/h3&gt;

&lt;p&gt;The first tool most teams reach for is AWS Instance Scheduler or a cron-based stop/start script. Set a schedule: stop at 8pm, start at 8am, skip weekends.&lt;/p&gt;

&lt;p&gt;This is better than nothing. But schedules are a proxy for human presence, not a measurement of it. A few failure modes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The schedule doesn't know about context.&lt;/strong&gt; An engineer working late to hit a sprint deadline finds their instance stopped mid-deploy. They manually restart it, forget to stop it again, and it runs all weekend anyway.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The schedule is wrong by default.&lt;/strong&gt; Vacations, public holidays, sick days, time-zone differences (one team member is in Tel Aviv, another in Berlin) — none of these are automatically reflected. A resource scheduled to run 8am–8pm for a team with no one logged in during a national holiday is still running for 12 hours for nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Schedules can't see meetings.&lt;/strong&gt; An engineer is in a 3-hour planning session. Their EC2 instance is fully up, fully billed. Nobody is touching it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Granularity is blunt.&lt;/strong&gt; You can stop an instance at a team level, but schedules don't distinguish between the engineer who starts at 7am and the one who starts at 10am. Either you over-provision (start at 7am for everyone) or someone is waiting for a cold start.&lt;/p&gt;

&lt;h3&gt;
  
  
  CloudWatch metrics
&lt;/h3&gt;

&lt;p&gt;The next layer up is metric-based automation: watch CPU, network I/O, or memory. If the instance has been below threshold for 30 minutes, stop it.&lt;/p&gt;

&lt;p&gt;This is more adaptive than schedules — but it measures the &lt;em&gt;resource&lt;/em&gt;, not the &lt;em&gt;human&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;An idle EC2 instance in a dev environment is almost always low-CPU. That's normal. Your developer is reading documentation in a browser, on a different machine, while a background process idles at 0.2% CPU. From the CloudWatch perspective: the instance looks idle. From the engineering perspective: the developer is mid-task and will need it in 20 minutes.&lt;/p&gt;

&lt;p&gt;Triggering a stop based on CPU alone causes phantom cold starts, broken tunnels, lost terminal sessions — and engineers who quickly learn to disable auto-stop.&lt;/p&gt;




&lt;h2&gt;
  
  
  The architectural gap: where is the human?
&lt;/h2&gt;

&lt;p&gt;Both approaches — schedules and server-side metrics — share the same blind spot. Neither one knows whether a &lt;em&gt;person&lt;/em&gt; is present and doing focused work.&lt;/p&gt;

&lt;p&gt;The relevant signal doesn't live in CloudWatch. It lives on the developer's machine: is their laptop open? Are they actively using work tools — their IDE, a terminal, a deploy pipeline? Have they moved their mouse in the last 10 minutes, or is the screen locked?&lt;/p&gt;

&lt;p&gt;This is what &lt;strong&gt;activity-driven&lt;/strong&gt; resource management addresses. Instead of inferring usage from server-side metrics or guessing from a calendar, an activity-driven system monitors the developer's local machine directly — detecting active presence and work-tool focus — and uses that signal to drive pause and resume decisions for their cloud resources.&lt;/p&gt;

&lt;p&gt;When the developer's machine goes idle (screen locks, laptop lid closes, sustained inactivity past a configurable threshold), their associated resources pause. When presence resumes — lid opens, tools come back into focus — resources resume before they're needed. The latency between signal and resumed resource should be low enough that the developer doesn't notice a cold start.&lt;/p&gt;




&lt;h2&gt;
  
  
  What this looks like in practice
&lt;/h2&gt;

&lt;p&gt;Here's the basic flow for an EC2 dev instance, broken down:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Developer machine                Cloud resources
─────────────────                ───────────────

[IDE in focus, terminal active]
        │
        │  ──────────── EC2: running ──────────────►
        │
[Joins a 2-hour meeting]
[Work tools go idle]
        │
        ├─ idle threshold crossed
        │
        │  ──────── EC2: pause initiated ──────────►
        │
[Meeting ends]
[IDE comes back into focus]
        │
        ├─ activity detected
        │
        │  ──────── EC2: resume initiated ──────────►
        │
[Back to work — instance ready]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For RDS: the same signal chain applies, but RDS has a longer warm-up time on resume (~3–10 min for a &lt;code&gt;db.t3&lt;/code&gt; class instance to accept connections). In practice this means either:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The system resumes RDS slightly ahead of EC2, using the IDE activity signal as an early trigger — so by the time the developer opens a DB client, the instance is ready.&lt;/li&gt;
&lt;li&gt;Or the threshold for RDS pause is set conservatively longer than for EC2, since RDS cold start cost is higher.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For ECS: pausing means stopping the service tasks (desired count → 0). Resume sets them back. This is appropriate for dev/staging services, not for anything serving live traffic. An activity-driven system should never touch production resources — that's a hard boundary.&lt;/p&gt;

&lt;p&gt;For ASG: the equivalent is min capacity → 0 on pause, restored to the team's configured minimum on resume.&lt;/p&gt;




&lt;h2&gt;
  
  
  Multi-account and multi-region teams
&lt;/h2&gt;

&lt;p&gt;This pattern gets more interesting at team scale.&lt;/p&gt;

&lt;p&gt;A platform team managing AWS resources across multiple accounts (dev, staging, sandbox accounts per squad) needs the pause/resume logic to follow &lt;em&gt;individual engineers&lt;/em&gt; to their respective resources, not apply a blanket schedule across the org.&lt;/p&gt;

&lt;p&gt;Engineer A is heads-down in their dev account in eu-west-1. Their resources stay up. Engineer B closed their laptop at 4pm on a Friday in us-east-1. Their resources pause — without affecting anyone else.&lt;/p&gt;

&lt;p&gt;This per-user, per-account granularity is what moves the billing needle. Org-wide schedules are a blunt instrument; per-user activity signals let the bill actually reflect the team's real working pattern.&lt;/p&gt;

&lt;p&gt;For FinOps leads or engineering managers, the useful output from this is a savings attribution view: which team, which account, which resources are actually getting paused, and for how long. Without that visibility, you're flying blind on whether the automation is working.&lt;/p&gt;




&lt;h2&gt;
  
  
  What to look for in an activity-driven setup
&lt;/h2&gt;

&lt;p&gt;If you're evaluating this approach — whether you're building it yourself or adopting a tool — the implementation details that matter:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Local agent reliability.&lt;/strong&gt; The presence-detection agent on the developer's machine needs to be lightweight and stable. A flapping agent that crashes every few days, or an agent that pegs a CPU core, will get disabled. It needs to run quietly in the background — macOS and Windows both — without requiring elevated privileges for normal operation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resume latency vs. pause aggressiveness.&lt;/strong&gt; Tuning the idle threshold is a UX problem as much as a cost problem. Too aggressive (5-minute idle threshold) and developers feel constant cold starts. Too conservative (2-hour threshold) and you barely move the bill. A configurable threshold per resource type — shorter for EC2 (fast resume), longer for RDS (slower resume) — gives the right balance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Safety guardrails.&lt;/strong&gt; The system must never pause a resource that's serving live traffic. That means either tag-based targeting (only pause resources tagged &lt;code&gt;environment=dev&lt;/code&gt; or &lt;code&gt;environment=staging&lt;/code&gt;) or an allowlist that the team explicitly maintains. ABAC over tags is the cleanest approach for multi-account setups.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Visibility.&lt;/strong&gt; Engineers will accept automated pausing if they can see what happened and why. A log that says "RDS paused: idle since 18:34" is the difference between trust and anger.&lt;/p&gt;




&lt;h2&gt;
  
  
  The honest bottom line
&lt;/h2&gt;

&lt;p&gt;There is no magic percentage to claim here. How much this approach moves your bill depends on your team's actual working patterns, your instance mix, and how aggressively you configure thresholds. Teams with offices in a single timezone and predictable hours will see a different profile than distributed teams with async cultures.&lt;/p&gt;

&lt;p&gt;What is consistent: in any environment where developers have dedicated dev EC2 instances and shared staging RDS databases, a significant fraction of runtime accrues while nobody is working. The fraction is empirically measurable once you have the activity signal — you can see exactly which resources paused, for how long, and correlate it against the billing data.&lt;/p&gt;

&lt;p&gt;Fixed schedules approximate this. Server-side metrics approximate it, badly. Activity-driven automation — pausing resources based on actual human presence, not a calendar or a CPU graph — is the structural fix.&lt;/p&gt;

&lt;p&gt;If you're running a dev/staging environment on AWS and want to see how this plays out in your account, Trigops offers an early-adopter plans. It installs a lightweight desktop agent (macOS and Windows), connects to your AWS accounts, and starts surfacing idle-time data within the first session. Worth a look: &lt;a href="https://trigops.com" rel="noopener noreferrer"&gt;trigops.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>devops</category>
      <category>finops</category>
      <category>cloud</category>
    </item>
    <item>
      <title>The ECS Task That Billed You All Night (And How to Think About Fixing It)</title>
      <dc:creator>Trigops</dc:creator>
      <pubDate>Thu, 28 May 2026 12:04:29 +0000</pubDate>
      <link>https://dev.to/trigops/the-ecs-task-that-billed-you-all-night-and-how-to-think-about-fixing-it-21b9</link>
      <guid>https://dev.to/trigops/the-ecs-task-that-billed-you-all-night-and-how-to-think-about-fixing-it-21b9</guid>
      <description>&lt;p&gt;Your ECS service spun up four tasks at 9 AM for a load test. By 11 AM the test was done, the results were in Slack, and everybody moved on. It's now 2 AM. Those four tasks are still running.&lt;/p&gt;

&lt;p&gt;Nothing is serving traffic. Nobody is waiting on them. The service health check is green because the tasks haven't crashed — they're just sitting there, healthy and idle and billing you $0.04048 per vCPU-hour and $0.004445 per GB-hour.&lt;/p&gt;

&lt;p&gt;This is not a rare edge case. It's the default behavior of most dev and staging environments, because the failure mode is invisible. The instance isn't broken. The service isn't alerting. The cost just... accumulates, quietly, until someone pulls the AWS bill at the end of the month and wonders where the extra $400 went.&lt;/p&gt;

&lt;p&gt;This article is about why that happens structurally — what ECS, EC2, and ASG are actually doing while your engineers sleep — and what a better model looks like.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why the problem is harder than it looks
&lt;/h2&gt;

&lt;p&gt;The naive fix is a schedule: shut things down at 7 PM, bring them back at 8 AM. This is what most teams reach for first, and it solves about 60% of the problem while introducing a different one.&lt;/p&gt;

&lt;p&gt;Schedules are an approximation of human behavior, not a model of it. Your engineers are not fungi; they don't follow a predictable diurnal rhythm. Someone is debugging a prod issue at 10 PM. Someone else is finishing a migration at 6 AM before a release. A team in a different timezone starts at noon your time.&lt;/p&gt;

&lt;p&gt;The moment you codify a schedule, you start getting exceptions. The 7 PM shutdown becomes 8 PM because someone complained. Then 9 PM. Then you add a "keep-alive" flag for certain instances. Then the schedule starts drifting per-team, per-environment, per-resource, and you've replaced a billing problem with a configuration management problem.&lt;/p&gt;

&lt;p&gt;The other failure mode is subtler: the schedule doesn't know what the resource is doing at shutdown time. Your EC2 dev box gets terminated mid-compile. Your staging RDS gets paused while a migration is running. Your ECS task for a long-running job gets killed at T-2 minutes. These aren't theoretical — they happen, and they erode trust in the automation fast enough that teams start disabling it.&lt;/p&gt;




&lt;h2&gt;
  
  
  What actually drives idle time
&lt;/h2&gt;

&lt;p&gt;To fix the problem properly, you have to model it correctly. Idle time in dev/staging environments comes from a few distinct sources:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Work-session gaps.&lt;/strong&gt; Engineers don't work in continuous eight-hour blocks. There are lunch breaks, meetings, context switches, and the natural rhythm of focused work followed by review and coordination. A dev EC2 box might be actively used for three or four focused hours in a given workday, with the rest of the time sitting warm but unused.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;End-of-day orphans.&lt;/strong&gt; The most common case: work ends, the engineer closes their laptop, and whatever AWS resources they were using keep running. No shutdown signal was sent. Nothing crashed. The resources just... persist.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test and load artifacts.&lt;/strong&gt; ECS tasks, RDS read replicas, and EC2 instances provisioned for a specific test run and not cleaned up afterward. The test finished, the PR merged, and nobody thought to tear down the environment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Over-provisioned ASGs.&lt;/strong&gt; Scale-out events during business hours don't automatically reverse when load drops. If your desired capacity isn't decremented or your scale-in policy is conservative (common, to avoid flapping), the extra instances sit at minimum capacity billing you for capacity you're not using.&lt;/p&gt;

&lt;p&gt;None of these are pathological. They're all rational behavior by engineers doing their jobs — and they add up fast at AWS prices.&lt;/p&gt;




&lt;h2&gt;
  
  
  The ASG case is worth examining in detail
&lt;/h2&gt;

&lt;p&gt;Auto Scaling Groups get special attention because they're where the invisible-cost problem is most structurally embedded.&lt;/p&gt;

&lt;p&gt;Here's a typical sequence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;09:00  ASG desired capacity: 2 (baseline)
13:00  Load test begins, scale-out triggered
13:15  ASG desired capacity: 6 (scale-out complete)
14:47  Load test ends
15:30  p99 latency back to baseline
17:00  Engineers sign off
23:00  ASG desired capacity: 6 (still)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why didn't it scale back in? Several reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scale-in policies typically have a cooldown (300 seconds by default) and require sustained low-CPU, not just low-CPU right now. A brief spike at 5:50 PM reset the cooldown.&lt;/li&gt;
&lt;li&gt;The target tracking policy is based on average CPU across the group. Four idle instances average out against two slightly-busy ones and the metric never triggers.&lt;/li&gt;
&lt;li&gt;Nobody set a scheduled scale-in for end-of-business because "we'll handle that in the next sprint."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result is that you're paying for four extra instances from 2:47 PM until someone manually adjusts desired capacity the next morning — roughly 16 hours. At &lt;code&gt;m5.large&lt;/code&gt; on-demand pricing in us-east-1, that's about $0.096/hour per instance, so 4 instances × 16 hours × $0.096 = roughly $6.14 for a single day. Multiply by the number of load-test days per month, and by the number of ASGs in your non-production account, and you're talking meaningful money.&lt;/p&gt;

&lt;p&gt;The fix that almost works: set a scheduled action to reset desired capacity to 2 at 8 PM. This is better than nothing. It still misses the load test that finishes at 7:30 PM (instances run until 8), and it breaks the engineer who is legitimately using those instances at 8:05 PM.&lt;/p&gt;




&lt;h2&gt;
  
  
  What "activity-driven" actually means
&lt;/h2&gt;

&lt;p&gt;The phrase "activity-driven" is worth defining precisely, because it gets used loosely.&lt;/p&gt;

&lt;p&gt;Activity-driven resource management means the pause/resume decision is based on detected real user presence and work-tool focus — what the engineer's machine is actively doing — rather than a clock or a server-side metric.&lt;/p&gt;

&lt;p&gt;This is different from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Schedule-based:&lt;/strong&gt; "Pause at 7 PM, resume at 8 AM" — no signal from the human.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metric-based:&lt;/strong&gt; "Pause when CPU &amp;lt; 5% for 30 minutes" — signal from the server, not the user. An idle EC2 box running a cron job looks identical to one nobody has touched.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manual:&lt;/strong&gt; "Click the stop button when you're done" — relies on engineers remembering, which they don't.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An activity-driven system detects that the engineer's machine has been idle or context-switched away from work tools for a meaningful interval, and uses that as the signal to pause the associated cloud resources. When they return — open their IDE, reconnect a terminal, push a commit — the resources resume automatically before they need them.&lt;/p&gt;

&lt;p&gt;The key property is that the signal comes from the human side of the equation, not the infrastructure side. This matters because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An RDS instance can be completely idle (zero connections, zero queries) while the engineer is mid-thought and about to run a migration. Metric-based systems see idle and pause; the engineer gets a connection timeout.&lt;/li&gt;
&lt;li&gt;An EC2 box can show 90% CPU because it's running a background compile while the engineer is in a three-hour meeting. Schedule-based systems keep it running; activity-driven systems can pause it when it's clear the engineer won't need output from that compile for hours.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  The RDS wrinkle
&lt;/h2&gt;

&lt;p&gt;RDS makes this harder because the resource has no meaningful idle signal at the instance level. An EC2 instance at 0% CPU is almost certainly idle. An RDS instance at 0% CPU might have an engineer staring at a half-written query in their SQL client, about to hit execute.&lt;/p&gt;

&lt;p&gt;The standard AWS tooling here is Instance Scheduler, which lets you define stop/start windows per tag. It's schedule-based, and it has the same failure mode described above — except with RDS the stakes are higher, because RDS startup time is non-trivial. A Multi-AZ RDS instance can take several minutes to become available after a stop. If someone's stop window is wrong and the database restarts mid-migration, you're looking at a real incident, not just a mild inconvenience.&lt;/p&gt;

&lt;p&gt;The billing structure also differs from EC2. When an RDS instance is stopped, you stop paying for instance hours — but you keep paying for storage (gp2/gp3 volume), backup storage, and any snapshot overhead. The Multi-AZ standby instance you provisioned for a staging environment that gets promoted to prod-like parity? That's billed at the same rate as the primary. Stopping the primary stops both, but storage keeps running.&lt;/p&gt;

&lt;p&gt;For most non-production RDS use cases, the right architecture is a stopped instance that restarts on demand, not a running instance that someone remembers to stop. The challenge is the "on demand" part — the startup latency makes manual workflows painful, which is why most teams don't do it.&lt;/p&gt;




&lt;h2&gt;
  
  
  What good looks like in practice
&lt;/h2&gt;

&lt;p&gt;A useful mental model: treat non-production cloud resources the way you treat your local dev environment. Your laptop doesn't run your IDE at full capacity all night. It sleeps. It wakes up fast when you need it. The resource is available when you're working and not burning compute when you're not.&lt;/p&gt;

&lt;p&gt;Applied to AWS:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An EC2 dev box stops within a few minutes of the engineer closing their last work session, and starts automatically when they open their IDE or terminal the next morning.&lt;/li&gt;
&lt;li&gt;An ECS staging service scales to zero when no engineers are actively working, and scales up before they connect — not after they notice a 502.&lt;/li&gt;
&lt;li&gt;An RDS instance used only for migration testing stops at end-of-day and restarts when someone connects.&lt;/li&gt;
&lt;li&gt;An ASG resets to baseline capacity when work-session signals across the team have been idle for a threshold interval, not on a fixed schedule.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this requires perfect engineering. It requires that the pause/resume signal be grounded in actual human activity rather than a wall-clock approximation of it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The multi-account visibility problem
&lt;/h2&gt;

&lt;p&gt;One thing that makes this worse at scale: non-production AWS resources often live in separate accounts from production, and visibility into those accounts is fragmented.&lt;/p&gt;

&lt;p&gt;Your staging account, dev sandbox, and QA environment might all be separate AWS accounts — the right security posture — but the cost data from all three flows through AWS Cost Explorer at the payer account level, tagged inconsistently, and nobody has a clear picture of what's actually running and idle at any given moment.&lt;/p&gt;

&lt;p&gt;Teams that are serious about this problem centralize the visibility first. Not just cost reports, but live resource state: what's running, who provisioned it, when it was last used, and what it's costing per hour. Without that view, you can't make good decisions about what to pause.&lt;/p&gt;

&lt;p&gt;The tooling for this has improved significantly, but the gap between "I can see the cost" and "I can act on it automatically" is still wide for most teams. That gap is where a large portion of the waste lives.&lt;/p&gt;




&lt;h2&gt;
  
  
  A note on what not to do
&lt;/h2&gt;

&lt;p&gt;Two approaches that seem reasonable and usually backfire:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Aggressive auto-termination.&lt;/strong&gt; Setting TTLs on dev resources and terminating them after X hours of inactivity is blunt and creates anxiety. Engineers start hoarding long-lived resources and tagging things to prevent deletion, which is the opposite of what you want. Pause, not terminate, is the right default for dev resources; the state is preserved and startup is fast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tagging-and-ask workflows.&lt;/strong&gt; Sending Slack messages like "your EC2 instance has been idle for 2 hours, should we stop it?" adds friction back to the engineer at exactly the moment they've context-switched away. Response rates on these are low, and the instances that matter most (the ones where someone is mid-task) are the ones most likely to get stopped by an inattentive yes-click.&lt;/p&gt;

&lt;p&gt;The goal is zero-friction automation that the engineer can trust, not a different version of manual.&lt;/p&gt;




&lt;p&gt;If this matches a problem you're dealing with in your non-production environments, Trigops is built around exactly this model — activity-driven pause and resume for EC2, RDS, ECS, and ASG, across multiple accounts. You can connect a cloud account and see what's actually running at &lt;a href="https://trigops.com" rel="noopener noreferrer"&gt;trigops.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>finops</category>
      <category>devops</category>
      <category>cloudcost</category>
    </item>
    <item>
      <title>Why Your EC2 Schedule Is a Lie (And What Activity-Driven Pause Actually Means)</title>
      <dc:creator>Trigops</dc:creator>
      <pubDate>Thu, 28 May 2026 09:56:21 +0000</pubDate>
      <link>https://dev.to/trigops/why-your-ec2-schedule-is-a-lie-and-what-activity-driven-pause-actually-means-1ma7</link>
      <guid>https://dev.to/trigops/why-your-ec2-schedule-is-a-lie-and-what-activity-driven-pause-actually-means-1ma7</guid>
      <description>&lt;p&gt;Your team set up a cron job. Probably six months ago, maybe longer. It stops EC2 dev instances at 10 PM and starts them again at 7 AM. Someone spent an afternoon wiring it up — a Lambda, a CloudWatch Events rule, maybe an AWS Instance Scheduler stack — and it felt like a win.&lt;/p&gt;

&lt;p&gt;It is a win. Compared to nothing, a fixed schedule is real savings. But here is what that schedule actually models: &lt;em&gt;everyone starts at exactly 7 AM, works continuously until exactly 10 PM, and nobody takes a lunch break, attends a three-hour meeting, or leaves early on a Friday.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Nobody works like that. And AWS bills by the second, so every gap between the schedule and reality is money leaving the account for compute that no human is using.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the Schedule Actually Covers
&lt;/h2&gt;

&lt;p&gt;Let's be concrete. Say your team is eight engineers in two time zones. The schedule runs Monday through Friday, 7 AM–10 PM in your primary zone. That's 15 hours of uptime per weekday, 0 on weekends.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Weekday uptime:  15 hours
Weekend uptime:   0 hours
Total per week:  75 hours (15 × 5)
Total per week, unmanaged: 168 hours

Schedule coverage: 75 / 168 = ~44.6%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fair. That schedule eliminated more than half the potential uptime. Now look at what it &lt;em&gt;doesn't&lt;/em&gt; cover.&lt;/p&gt;

&lt;p&gt;A senior engineer in your western office starts at 9 AM local time — two hours after the instance is already running. She has a two-hour all-hands on Tuesday. She's out sick Thursday. Her instance is running and billing from 7 AM regardless.&lt;/p&gt;

&lt;p&gt;Another engineer wraps up by 5 PM most days. The instance runs until 10 PM.&lt;/p&gt;

&lt;p&gt;Multiply this across eight engineers. You're running instances for stretches that map to no human work at all — not nights or weekends, but the quieter idle time baked into every real workday: mornings before anyone logs in, lunch hours, meetings, end-of-day gaps before the cron fires.&lt;/p&gt;

&lt;p&gt;The schedule didn't solve idle time. It solved &lt;em&gt;predictable&lt;/em&gt; idle time. The unpredictable kind — the kind that follows actual human behavior — it can't touch.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Fixed Schedules Can't Go Further
&lt;/h2&gt;

&lt;p&gt;The instinct when you notice this is to tighten the schedule. Push the start time later, pull the stop time earlier, add a weekend rule. But this runs into a hard wall fast.&lt;/p&gt;

&lt;p&gt;Schedules are global. A tighter window that works for your latest-starting engineer leaves your earliest engineer waiting for their instance. You can try per-engineer schedules, but now you're maintaining eight sets of CloudWatch rules and someone's schedule is always wrong after a timezone change or a hire.&lt;/p&gt;

&lt;p&gt;More importantly: schedules are static models of a dynamic thing. Human presence during a workday is not a block. It's a pattern of focus, interruption, and absence that changes every day. A meeting runs long. Someone gets pulled onto an incident. The schedule doesn't know any of this.&lt;/p&gt;

&lt;p&gt;The other approach — server-side metrics — doesn't solve it either. Low CPU on an EC2 instance means the machine isn't doing much, but it doesn't tell you whether an engineer is actively working in their IDE with the instance open in a terminal. Background processes, keep-alive connections, and idle shells all keep metrics above zero without any human present. You end up with false positives that train your team not to trust the automation, so they disable it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Activity-Driven: What It Actually Means
&lt;/h2&gt;

&lt;p&gt;Activity-driven pause/resume — the approach Trigops takes — means the signal for stopping or starting a resource comes from observed human presence and work-tool focus on the engineer's local machine, not from a clock or from server-side metrics.&lt;/p&gt;

&lt;p&gt;The practical shape of this: a lightweight desktop agent on the engineer's machine watches whether they're actively using tools associated with that instance (terminal sessions, IDE connections, AWS console activity). When the engineer stops working — closes the session, switches to Slack and stays there, locks their screen — the agent detects the transition and signals the associated EC2 instance to pause. When they return to active work, the instance resumes.&lt;/p&gt;

&lt;p&gt;This is not a schedule. There is no 10 PM rule. The instance pauses when &lt;em&gt;this engineer, today, right now&lt;/em&gt; is not using it — and resumes when they are.&lt;/p&gt;

&lt;p&gt;The result is that idle time tracks the actual gaps in each engineer's day, not a shared approximation of when the whole team might be working.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Architecture Difference
&lt;/h2&gt;

&lt;p&gt;Here is how the two approaches compare at the decision point — the moment of deciding whether to stop a resource:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Fixed schedule (CloudWatch Events / Instance Scheduler)&lt;/span&gt;

Input:  current time
Rule:   if time == "22:00", stop
Result: all instances in scope stop, regardless of who is using them&lt;span class="sb"&gt;


&lt;/span&gt;&lt;span class="gh"&gt;# Activity-driven (Trigops)&lt;/span&gt;

Input:  desktop agent signal — engineer X is idle / active
Rule:   if engineer X is idle, pause engineer X's associated resources
Result: that engineer's EC2 (or ECS service, RDS, ASG) pauses;
        engineer Y, who is still working, is unaffected
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The schedule operates at the account or tag level. Activity-driven operates at the engineer level and propagates to their resources.&lt;/p&gt;

&lt;p&gt;This matters for multi-account and multi-region teams. If you have a platform team running dev infrastructure across several AWS accounts, a schedule either covers all of them identically or you're managing a proliferation of rules. Activity-driven control stays coherent at the per-engineer, per-resource level regardless of how many accounts or regions the resources live in.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Resources This Applies To
&lt;/h2&gt;

&lt;p&gt;The resources where this makes the most practical difference are the ones tied to individual developer workflows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;EC2 dev boxes&lt;/strong&gt; are the clearest case. One engineer, one instance, a direct mapping between human presence and resource need. When the engineer isn't there, the instance doesn't need to run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ECS services in dev/staging&lt;/strong&gt; are trickier with schedules because they often have dependencies and minimum task counts that make blanket stop/start risky. Activity-driven control can scope the pause to a specific service without touching the rest of the cluster, and it only fires when nobody is actively using that service — not at an arbitrary time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RDS instances&lt;/strong&gt; are expensive to run continuously and slow to stop/start, which is exactly why teams avoid scheduling them: the risk of stopping a shared staging database mid-use is real. Activity-driven control, tied to whether anyone has an active connection through their workstation, provides the signal that a schedule can't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Auto Scaling Groups&lt;/strong&gt; are the most complex case. The minimum capacity floor that someone set before a demo is now a permanent floor that nobody reviews. Activity-driven approaches can inform when the floor can safely drop — not by overriding the ASG min directly, but by detecting when no engineer is actively using the environment and signaling a scale-down.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Real Cost of "Good Enough"
&lt;/h2&gt;

&lt;p&gt;A fixed schedule is not wrong. For a solo project or a team with very predictable hours, it might be genuinely sufficient. The argument here is not that schedules are bad — it's that they have a ceiling, and that ceiling is lower than most teams assume.&lt;/p&gt;

&lt;p&gt;The ceiling becomes visible when you look at instance-hours. A schedule that runs 7 AM–10 PM captures the nights and weekends. The instance-hours lost to within-day idle time — meetings, gaps, asynchronous work patterns, time zones — sit inside the schedule window and are invisible to it.&lt;/p&gt;

&lt;p&gt;Those hours are not catastrophic individually. Across a team over a month, they add up to a billing line that the schedule was supposed to eliminate.&lt;/p&gt;




&lt;h2&gt;
  
  
  Replacing the Cron
&lt;/h2&gt;

&lt;p&gt;The operational argument for dropping the fixed schedule in favor of activity-driven control is not just cost. It's maintenance.&lt;/p&gt;

&lt;p&gt;Cron jobs and CloudWatch rules have owners, and owners leave. The schedule that's running your EC2 stop/start today was probably set up by someone who has since moved to a different role or left the company. The logic lives in a Lambda function in a region someone had to remember to check. The schedule is six months stale and nobody is confident enough to change it.&lt;/p&gt;

&lt;p&gt;Activity-driven automation has no schedule to go stale. The pause/resume decision is made fresh on every session, based on what the engineer is actually doing. There is nothing to update when a new hire joins or when a team shifts to async-first.&lt;/p&gt;

&lt;p&gt;You stop managing a schedule. The resources manage themselves around the people using them.&lt;/p&gt;




&lt;p&gt;If your team is running EC2 dev boxes, staging ECS services, or shared RDS instances on a fixed stop/start schedule — or no schedule at all — Trigops does the activity-driven layer. It connects to your AWS accounts, maps resources to the engineers who use them, and pauses them when those engineers are idle. No cron required.&lt;/p&gt;

&lt;p&gt;trigops.com — connect a cloud account and see which of your resources are running when nobody is home.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>devops</category>
      <category>finops</category>
      <category>cloud</category>
    </item>
  </channel>
</rss>
