DEV Community

kazuho cryer-shinozuka for AWS Community Builders

Posted on AI-assisted

What does your CDK app cost when nobody is using it?

Here is a sample CDK app with four stacks. Nobody is calling it. No requests, no data, no users.

ap-northeast-1 · 4 stacks · idle ≈ $382/mo

DataStack                                    $196/mo
  Aurora/writer                 ×0.5 ACU      $55  Serverless v2 minimum capacity
  Search                        ×1            $41  t3.small.search data nodes
  Search                        ×20 GB gp3  $2.93  EBS storage
  Db                            ×1            $36  db.t4g.micro postgres Multi-AZ
  Db                            ×20 GB gp3  $5.52  storage
  ...

NetworkStack                                  $69/mo
  Vpc/PublicSubnet1/NATGateway  ×1            $45  NAT Gateway
  Vpc/SsmEndpoint               ×2 AZ         $20  interface endpoint
  Vpc/PublicSubnet1/EIP         ×1          $3.65  public IPv4 address

? could not estimate: 1
  AppStack  ParamInstance  InstanceType is Parameter WorkerType

prices: bundled snapshot 2026-10-15 · on-demand list prices, 730 h/mo, USD, free tier ignored
Enter fullscreen mode Exit fullscreen mode

It still costs about $382 a month. That output came from two commands:

cdk synth
npx cfn-idle-cost
Enter fullscreen mode Exit fullscreen mode

cfn-idle-cost reads your cdk.out and prints the idle cost of the app: what you pay even when nobody uses it.

Why idle cost?

Most surprise AWS bills I have seen were not caused by traffic. They were caused by things that bill by the hour whether or not anyone uses them:

  • a NAT Gateway in every AZ of a dev VPC
  • interface endpoints, billed per AZ
  • an ALB in front of a service that gets ten requests a day
  • an RDS instance someone set to Multi-AZ "to be safe"
  • an Aurora Serverless v2 cluster that never scales below its minimum ACU
  • KMS keys, Secrets Manager secrets and CloudWatch alarms that add up one dollar at a time

In CDK, a single new ec2.Vpc(this, 'Vpc') creates NAT Gateways for you. That default is reasonable for production, but it is easy to miss when you copy the same stack into a sandbox account or a per-developer environment.

Cost Explorer tells you about this after the bill arrives. The AWS Pricing Calculator can tell you before, but you have to enter every resource by hand. cfn-idle-cost reads the template you already have.

What it does

  • Reads cdk.out directly, including Stages and nested stacks. Plain CloudFormation templates (JSON or YAML) work too.
  • Names each line by construct path (Vpc/PublicSubnet1/NATGateway), so you know which line of CDK code to change.
  • Runs offline with no AWS credentials. Prices come from a snapshot of the public AWS Bulk Price List bundled in the package and refreshed weekly. The same cdk.out gives the same numbers for a given version. Pass --live if you want current prices from the Price List Query API instead.
  • Never guesses. If a value can't be read from the template, such as a Parameter without a default or a reference to another stack, the resource is listed under ? could not estimate with the reason. A Parameter with a Default is estimated with that default, and the line says so.

It covers more than 70 resource types: NAT Gateways, EIPs, VPC endpoints, load balancers, EC2 and Auto Scaling groups, RDS and Aurora, OpenSearch, ElastiCache, DynamoDB provisioned capacity, Kinesis, Fargate services, EFS and FSx, EKS, Network Firewall, WAF, Lambda provisioned concurrency, Transit Gateway attachments, MWAA, and more. The full list is in the README.

Usage-only charges are left out on purpose: S3, Lambda invocations, Aurora storage and I/O, CloudWatch Logs, data transfer, requests. Those depend on traffic, and this tool answers a different question: what is the floor?

Catch it in the pull request

Knowing the current idle cost is useful. Knowing that a pull request is about to add $300 a month is more useful.

--base compares two synthesized apps and prints only what changes:

npx cfn-idle-cost --base cdk.out.main cdk.out
Enter fullscreen mode Exit fullscreen mode
ap-northeast-1 · idle ≈ $382 → $682/mo (+$300)

  ~ DataStack     Db                              $42 →  $322   +$280  db.t4g.micro postgres Multi-AZ → db.t4g.large postgres Multi-AZ
  + NetworkStack  Vpc/PublicSubnet2/NATGateway  $0.00 →   $45    +$45  NAT Gateway
  - DataStack     Stream                          $28 → $0.00    -$28  provisioned shards
  + NetworkStack  Vpc/PublicSubnet2/EIP         $0.00 → $3.65  +$3.65  public IPv4 address
Enter fullscreen mode Exit fullscreen mode

There is a GitHub Action that posts this as a pull request comment, updates it on every push, and fails the check when the increase is over your budget:

name: idle-cost
on: pull_request
permissions: { contents: read, pull-requests: write }
jobs:
  idle-cost:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
        with: { path: head }
      - uses: actions/checkout@v7
        with: { path: base, ref: "${{ github.base_ref }}" }
      - uses: actions/setup-node@v7
        with: { node-version: 22 }
      - run: npm ci && npx cdk synth -q
        working-directory: head
      - run: npm ci && npx cdk synth -q
        working-directory: base
      - uses: badmintoncryer/cfn-idle-cost@v1
        with: { base: base/cdk.out, path: head/cdk.out, max-increase: 50 }
Enter fullscreen mode Exit fullscreen mode

Now the reviewer sees "+$280/mo: db.t4g.micro → db.t4g.large" next to the diff, instead of finding out from the bill a month later. Commit cdk.context.json so the synth doesn't need AWS credentials for lookups.

Outside GitHub Actions, --max <usd> and --max-increase <usd> make the CLI exit with code 3 when the budget is exceeded, so it fits into any CI.

What the numbers mean

  • On-demand list prices in USD, 730 hours a month.
  • Savings Plans, Reserved Instances and the free tier are ignored.
  • EC2 is priced as Linux with shared tenancy, because the AMI is not read.
  • An Auto Scaling group uses DesiredCapacity when set, otherwise MinSize.

In other words, it is the price list applied to your template, not a forecast of your bill. If you have a Savings Plan, your real number will be lower. That is fine: the point is to see what you are paying for and which construct it comes from.

Try it

cdk synth
npx cfn-idle-cost
Enter fullscreen mode Exit fullscreen mode

You can also install it with Go (go install github.com/badmintoncryer/cfn-idle-cost@latest), and cdk-idle-cost is the same tool under another name. The npm packages cover Linux and macOS; on Windows, use go install or the binary from the GitHub Release.

I'd love to hear what number your app prints. If it lists a resource under ! not counted yet that matters to you, or if a price looks wrong, please open an issue. And if it saves you a NAT Gateway or two, a ⭐ on GitHub helps others find it.


I also maintain two other tools that read cdk.out before you deploy: cdk-preflight fails cdk synth on templates that are known to fail at deploy time, and cfn-exec-policy generates a least-privilege IAM policy for the CloudFormation execution role, so cdk bootstrap doesn't have to use AdministratorAccess. More on those in later posts.

Top comments (0)