Sunday, August 23, 2026

CDK Cost Killer — tear down the stacks that keep billing

Forgotten CDK labs keep billing after you stop caring about them. cdk-cost-killer is a small CDK app that finds those stacks and tears them down — nightly for hygiene, and hourly only while you are already over budget.

It does not create a budget. It reads the account budget named Monthly budget (default limit $30; the live AWS Budgets number wins). The whole design is one Lambda, two schedules, and a few hard skip rules.

The loop

EventBridge Scheduler
   │
   ├─ 9:00 PM America/New_York  → action=kill, reason=nightly
   └─ every hour                → action=kill, reason=hourly
                                      │
                                      ▼
                              cdk-cost-killer Lambda
                                      │
                    ┌─────────────────┴─────────────────┐
                    │ read Monthly budget (actual vs cap)│
                    └─────────────────┬─────────────────┘
                                      │
              hourly + under budget? ─┤── yes → return (no-op)
                                      │
                                      ▼
                         scan enabled regions
                         group nested stacks by root
                         stop EC2 first, then DeleteStack

A third path is optional: subscribe the stack output BudgetAlertTopicArn to the budget’s 100% actual alert. SNS then invokes the same Lambda with reason=budget-notification instead of waiting for the next hourly check.

What is expensive enough to kill

A stack family is torn down only when it holds a resource that keeps costing money while it exists: EC2 instances, Elastic IPs, NAT gateways, VPC endpoints, load balancers, Auto Scaling groups, RDS, Redshift, ElastiCache, OpenSearch, EKS, ECS services.

Default scope is CDK only (AWS::CDK::Metadata or a CDK description). Set targetScope to all-cfn if you want any CloudFormation stack with those resources.

Always skipped:

  • this CdkCostKiller stack
  • CDKToolkit bootstrap stacks
  • anything tagged CostKillerProtect=true

EC2 in a doomed family is stopped first so compute charges drop while CloudFormation delete finishes. Elastic IPs and load balancers only stop costing money after the stack is gone.

Safety switches

Knob Default Why it exists
dryRun true Log would-stop / would-delete only. Arm after CloudWatch review.
enabled true Redeploy false to DISABLE both schedules and make the Lambda a no-op.
protectTagKey CostKillerProtect Tag keepers. Nested stacks inherit the root family’s decision.
regions all enabled Optional allowlist, e.g. us-east-1,us-east-2.
Hourly is not a second nightly. The hourly pass reads the live budget and bails if spend is still under the cap. After the billing period resets, those invocations become no-ops again. Nightly still runs regardless — that is the “I forgot to destroy the lab” cleanup.
npx cdk deploy                    # dry-run on by default
npx cdk deploy -c dryRun=false    # arm after reviewing logs
npx cdk deploy -c enabled=false   # off switch, stack stays

Tags.of(stack).add("CostKillerProtect", "true");

Source: github.com/jmjava/cdk-cost-killer

No comments: