CloudBurn Logo
CloudBurnHome
  • Cost estimation

    • AWS Simple Pricing CalculatorEstimate your AWS stack
    • IaC Cost EstimatorReview costs before merge

    Cost analysis

    • AWS Cost TreeSee where your spend went

    Cost Optimization

    • Unused resourcesFind resources to remove
    • Resource efficiencyImprove what you keep
    • Commitment savingsReview plans and reservations
    • Cost GovernanceBudgets, alerts, and tags

    Product

    • RoadmapWhat's coming next
    • ChangelogProduct updates
    • DocsGuides and reference
    • AboutWhy CloudBurn exists
  • Pricing
  • Blog
  • Tools
Join the CloudBurn Discord community
Join the waitlistWaitlist
Join the CloudBurn Discord community1.8k
Navigation
    • Overview
    • Installation
    • Configuration
    • GitHub Action
    • MCP Plugin
    • VS Code Extension
      • Scan IaC Files
      • Discover AWS
      • scan
      • discover
      • config
      • rules list
      • completion
      • estimate
      • Exit Codes
      • Output Formats
      • CI/CD Integration
      • Resource Explorer Setup
    • Overview
    • Connect an AWS Account
    • Set Up PR Cost Estimation
    • Plans and Billing
    • Workspaces and Members
    • AWS Cost Tree
      • Usage Assumptions
      • Examples
      • Unused Resources
      • Resource Efficiency
      • Commitment Savings
      • Cost Governance
    • Overview
    • Understanding Rules
      • CloudFront
      • CloudTrail
      • CloudWatch
      • AWS Config
      • Cost Explorer
      • Cost Guardrails
      • Cost Optimization Hub
      • DynamoDB
      • EBS
      • EC2
      • ECR
      • ECS
      • EKS
      • ElastiCache
      • ELB
      • EMR
      • KMS
      • Lambda
      • RDS
      • Redshift
      • Route 53
      • S3
      • SageMaker
      • Secrets Manager
      • Tagging
    • Overview
    • Installation
      • Static IaC Scan
      • Discovery Scan
      • CloudBurnClient
      • Types
      • Package Exports
      • Custom Config
      • AWS Credentials
    • Overview
    • Installation
    • Configuration
    • GitHub Action
    • MCP Plugin
    • VS Code Extension
      • Scan IaC Files
      • Discover AWS
      • scan
      • discover
      • config
      • rules list
      • completion
      • estimate
      • Exit Codes
      • Output Formats
      • CI/CD Integration
      • Resource Explorer Setup
    • Overview
    • Connect an AWS Account
    • Set Up PR Cost Estimation
    • Plans and Billing
    • Workspaces and Members
    • AWS Cost Tree
      • Usage Assumptions
      • Examples
      • Unused Resources
      • Resource Efficiency
      • Commitment Savings
      • Cost Governance
    • Overview
    • Understanding Rules
      • CloudFront
      • CloudTrail
      • CloudWatch
      • AWS Config
      • Cost Explorer
      • Cost Guardrails
      • Cost Optimization Hub
      • DynamoDB
      • EBS
      • EC2
      • ECR
      • ECS
      • EKS
      • ElastiCache
      • ELB
      • EMR
      • KMS
      • Lambda
      • RDS
      • Redshift
      • Route 53
      • S3
      • SageMaker
      • Secrets Manager
      • Tagging
    • Overview
    • Installation
      • Static IaC Scan
      • Discovery Scan
      • CloudBurnClient
      • Types
      • Package Exports
      • Custom Config
      • AWS Credentials
Loading documentation page
CloudBurn Logo
CloudBurn

Open-source AWS cost checks and an upcoming platform for cost estimation, optimization, and governance.

1.8k

Product

  • Pricing
  • Roadmap
  • Changelog
  • About
  • Blog
  • Waitlist
  • Docs
  • Contact

Free Tools

  • EC2 Pricing Calculator
  • S3 Pricing Calculator
  • Bedrock Pricing Calculator
  • Fargate Pricing Calculator
  • RDS Pricing Calculator
  • ECS Pricing Calculator
  • Aurora Pricing Calculator
  • All AWS pricing calculators →

CloudBurn Platform · early 2027

Price changes before you deploy. Fix the waste after.

Join now for 30% off, for as long as you keep your plan.

Join the waitlist

Two emails a month at most. Unsubscribe anytime. By signing up you agree to the privacy policy.

CloudBurn © 2026 | Terms & Privacy

Built with ❤️ by Towards the Cloud

  • Pricing
  • Blog
  • Tools
      • Overview
      • Connect an AWS Account
      • Set Up PR Cost Estimation
      • Plans and Billing
      • Workspaces and Members
      • AWS Cost Tree
        • Usage Assumptions
        • Examples
      • Overview
      • Connect an AWS Account
      • Set Up PR Cost Estimation
      • Plans and Billing
      • Workspaces and Members
      • AWS Cost Tree
        • Usage Assumptions
        • Examples
    CloudBurn Platform

    Usage Assumption Examples

    Worked .cloudburn/usage-assumptions.json configurations for serverless, storage, observability, streaming, and network workloads.

    A usage assumptions file is only as good as the numbers in it. This page shows complete, copyable configurations for the workload shapes CloudBurn prices, explains where each number comes from in your own account, and describes the feedback loop that tells you when a number is missing.

    Every field, unit, pool, and limit used here is defined in the usage assumptions schema.

    The three parts of a working config

    Every file that produces a complete estimate answers three questions:

    1. What does this resource do per month? That is the assumption field — lambda.invocations, s3.storage.standardGbMonth, natGateway.processedGb.
    2. Where does the account already sit in the AWS tier? That is accountContext.usage. Any component whose AWS price has more than one tier stays unknown without it. 0 is a valid answer.
    3. Is there free tier left, and how much? That is freeTier plus accountContext.remainingFreeTier. Leave freeTier at its "disabled" default on an established account.

    Miss part 1 and one component goes unknown. Miss part 2 and a graduated component goes unknown even though you supplied the quantity. That second case is the one people hit first, so the examples below always show the pools.

    Where each number comes from

    Pull real numbers from the account that already runs the workload, or from the closest environment you have. Cost Explorer grouped by Usage Type with daily granularity gives you the figure AWS actually bills, which is what these fields want.

    FieldSource in your account
    s3.storage.*GbMonthCloudWatch AWS/S3 → BucketSizeBytes, filtered by the matching StorageType (StandardStorage, DeepArchiveStorage, IntelligentTieringFAStorage, …)
    s3.requests.tier1 / tier2Cost Explorer usage types Requests-Tier1 and Requests-Tier2, or S3 request metrics if you have them on
    s3.dataTransfer.outToInternetGbCost Explorer, DataTransfer-Out-Bytes for the bucket's Region
    sqs.requests, sns.*Cost Explorer usage type for the queue or topic — already normalized to 64 KB request units
    logs.ingestedGbCloudWatch AWS/Logs → IncomingBytes (Sum) for the log group
    logs.storedGbaws logs describe-log-groups → storedBytes
    efs.storage.*GbMonthCloudWatch AWS/EFS → StorageBytes by storage class; add IASizeOverhead to IA and ArchiveSizeOverhead to Archive for metered bytes
    lambda.invocationsCloudWatch AWS/Lambda → Invocations (Sum) per function
    lambda.averageDurationMsCloudWatch AWS/Lambda → Duration (Average) per function
    apigateway.requestsCloudWatch AWS/ApiGateway → Count: REST APIs use SampleCount; HTTP APIs use Sum
    kinesis.onDemand.ingestedGbCloudWatch AWS/Kinesis → IncomingBytes (Sum)
    kinesis.onDemand.retrievedGbCloudWatch AWS/Kinesis → GetRecords.Bytes (Sum)
    natGateway.processedGbCloudWatch AWS/NATGateway → BytesInFromSource + BytesInFromDestination (Sum)
    resolver.queriesRoute 53 Resolver query logs, or Cost Explorer usage type for the endpoint
    accountContext.usage.*Cost Explorer for the whole account, grouped by Usage Type, for the same month
    accountContext.remainingFreeTier.*Billing console → Free tier page, "Usage remaining this month"

    Average storage measurements across the month before converting bytes to GB-Mo; a single snapshot is only an estimate. Sum traffic metrics over the month and convert bytes to GB.

    Two fields need normalizing before they go in the file, and both trip people up: SQS and SNS take billable request units rather than message counts, and lambda.averageDurationMs is a per-invocation average rather than a monthly total. The SQS and Lambda sections of the schema reference give the exact conversions.

    A minimal starter

    The smallest useful file: one function, one pool per graduated component, no free tier.

    {
      "schemaVersion": 2,
      "revision": "2026-08-01",
      "accountContext": {
        "usage": {
          "lambda.requests": 0,
          "lambda.duration.x86": 0
        }
      },
      "resources": {
        "ApiStack/OrdersHandler": {
          "lambda": { "invocations": 4000000, "averageDurationMs": 180 }
        }
      }
    }
    

    The function's declared MemorySize and Architectures come from the diff, so those two assumptions are all the report needs to derive compute GB-seconds — and it shows the arithmetic in the component explanation. Change the memory in a later pull request and the report attributes the delta to the infrastructure input rather than to your assumptions.

    Serverless API

    A realistic serverless stack: an HTTP API in front of two functions, a queue, and a fan-out topic. Note the per-architecture Lambda pools — an arm64 function advances lambda.duration.arm, never the x86 pool.

    {
      "schemaVersion": 2,
      "revision": "2026-08-01",
      "accountContext": {
        "usage": {
          "lambda.requests": 120000000,
          "lambda.duration.x86": 900000,
          "lambda.duration.arm": 2400000,
          "apigateway.requests.http": 45000000,
          "sqs.requests": 60000000,
          "sns.requests.standard": 8000000
        }
      },
      "resourceTypeDefaults": {
        "AWS::Lambda::Function": {
          "lambda": { "invocations": 500000, "averageDurationMs": 120 }
        }
      },
      "resources": {
        "ApiStack/PublicApi": {
          "apigateway": { "requests": { "value": 30, "unit": "million-requests" } }
        },
        "ApiStack/OrdersHandler": {
          "lambda": { "invocations": 30000000, "averageDurationMs": 210 }
        },
        "ApiStack/ReceiptRenderer": {
          "lambda": { "invocations": 2000000, "averageDurationMs": 1400 }
        },
        "ApiStack/OrdersQueue": {
          "sqs": { "requests": { "value": 90, "unit": "million-requests" } }
        },
        "ApiStack/OrderEventsTopic": {
          "sns": {
            "publishedMessages": { "value": 30, "unit": "million-requests" },
            "deliveries": { "sqs": { "value": 30, "unit": "million-requests" } }
          }
        }
      }
    }
    

    Two details worth copying. The resourceTypeDefaults entry keeps every other function in the diff from reporting as unknown — a new helper function added in a later pull request gets a defensible placeholder instead of a hole in the estimate. And sns.deliveries.sqs is stated on the topic alongside the publish count: SNS delivery to SQS is a separate billable dimension, and omitting it would leave that component unknown rather than free. It needs no accountContext entry, because AWS prices it at a single rate with no tiers.

    For a WebSocket API, swap the request field for the two WebSocket fields and their pools — they bill separately, so state a real zero rather than omitting one:

    {
      "accountContext": {
        "usage": {
          "apigateway.websocket.messages": 0,
          "apigateway.websocket.connectionMinutes": 0
        }
      },
      "resources": {
        "RealtimeStack/EventsSocket": {
          "apigateway": {
            "websocket": {
              "messages": { "value": 12, "unit": "million-messages" },
              "connectionMinutes": { "value": 4, "unit": "million-minutes" }
            }
          }
        }
      }
    }
    

    Storage with lifecycle transitions

    An archival data lake exercises the part of S3 pricing that surprises people: the transition requests and the retrieval charges, not the storage rate.

    {
      "schemaVersion": 2,
      "revision": "2026-08-01",
      "accountContext": {
        "usage": {
          "s3.storage.standard": 51200,
          "s3.storage.standardIa": 0,
          "s3.storage.deepArchive": 180000,
          "s3.requests.standardTier1": 12000000,
          "s3.requests.standardTier2": 90000000,
          "s3.lifecycleTransitions.deepArchive": 400000,
          "s3.retrieval.deepArchive": 250,
          "awsDataTransfer.outboundInternet": 8000
        }
      },
      "repositoryDefaults": {
        "s3": { "requests": { "tier2": { "value": 5, "unit": "million-requests" } } }
      },
      "resourceTypeDefaults": {
        "AWS::S3::Bucket": {
          "s3": { "storage": { "standardGbMonth": 100 } }
        }
      },
      "resources": {
        "StorageStack/RawEventsBucket": {
          "s3": {
            "storage": {
              "standardGbMonth": { "value": 2, "unit": "TB" },
              "deepArchiveGbMonth": 10000
            },
            "requests": { "tier1": { "value": 200, "unit": "thousand-requests" } },
            "lifecycleTransitions": { "deepArchive": { "value": 50, "unit": "thousand-requests" } },
            "retrieval": { "deepArchiveGb": 50 },
            "dataTransfer": { "outToInternetGb": 1000 }
          }
        },
        "module.analytics.aws_s3_bucket.curated": {
          "s3": {
            "storage": { "standardIaGbMonth": 4000 },
            "requests": { "tier2": { "value": 20, "unit": "million-requests" } }
          }
        }
      }
    }
    

    RawEventsBucket overrides the resource-type storage default and adds archival storage, destination-specific transitions, retrieval, and outbound transfer. Every other bucket in the diff inherits 100 GB of Standard storage and 5 million Tier 2 requests from the broader layers. The Terraform address in the same file is deliberate: one repository can hold CDK and Terraform addresses side by side.

    The transitions field names its destination. s3.requests.lifecycleTransition without a destination reports as unknown, because AWS charges a different rate per destination class — and so does s3.dataTransfer.gb without a direction.

    Observability and shared storage

    CloudWatch Logs and EFS both split into classes that bill under separate dimensions, and both refuse to infer one class from another.

    {
      "schemaVersion": 2,
      "revision": "2026-08-01",
      "accountContext": {
        "usage": {
          "logs.ingestion.standard": 2400,
          "logs.ingestion.infrequentAccess": 600,
          "logs.storage": 18000,
          "efs.storage.standard": 3000,
          "efs.storage.infrequentAccess": 12000,
          "efs.storage.archive": 40000,
          "efs.access.infrequentAccess": 500,
          "efs.access.archive": 100
        }
      },
      "resourceTypeDefaults": {
        "AWS::Logs::LogGroup": {
          "logs": { "ingestedGb": 5, "storedGb": 20 }
        }
      },
      "resources": {
        "PlatformStack/ApiAccessLogs": {
          "logs": { "ingestedGb": 320, "storedGb": 1900 }
        },
        "PlatformStack/AuditTrailLogs": {
          "logs": { "ingestedGb": 900, "storedGb": 10800 }
        },
        "PlatformStack/SharedAssets": {
          "efs": {
            "storage": {
              "standardGbMonth": 250,
              "infrequentAccessGbMonth": 4000,
              "archiveGbMonth": 20000
            },
            "access": {
              "infrequentAccessReadGb": 120,
              "infrequentAccessWriteGb": 0,
              "archiveReadGb": 5,
              "archiveWriteGb": 900
            }
          }
        }
      }
    }
    

    AuditTrailLogs is declared with LogGroupClass: INFREQUENT_ACCESS in the stack, so its ingestion prices against the Infrequent Access dimension and claims nothing from the Standard free tier. Storage bills under one dimension for both classes.

    The EFS access fields show the rule that catches people: reads and writes bill separately per class, so "infrequentAccessWriteGb": 0 is a statement, not a placeholder. Omitting it would leave that component unknown. A file system that transitions data into Archive but supplies no archiveGbMonth reports Archive storage as unknown — CloudBurn never invents the quantity from the lifecycle policy.

    Streaming and network egress

    Network is where "just give me a number" fails hardest, because every dimension needs an explicit direction or geography.

    {
      "schemaVersion": 2,
      "revision": "2026-08-01",
      "accountContext": {
        "usage": {
          "awsDataTransfer.outboundInternet": 22000,
          "resolver.queries": 900000000
        }
      },
      "resources": {
        "IngestStack/EventStream": {
          "kinesis": {
            "onDemand": { "ingestedGb": 3200, "retrievedGb": 9600 }
          }
        },
        "IngestStack/AnalyticsConsumer": {
          "kinesis": { "enhancedFanOut": { "retrievedGb": 3200 } }
        },
        "NetworkStack/EgressNat": {
          "natGateway": { "processedGb": 14000 }
        },
        "NetworkStack/EdgeAccelerator": {
          "globalAccelerator": {
            "dataTransfer": {
              "inboundFromEuropeGb": 800,
              "outboundToEuropeGb": 6400,
              "inboundFromNorthAmericaGb": 300,
              "outboundToNorthAmericaGb": 2100
            }
          }
        },
        "NetworkStack/CorporateVpn": {
          "vpn": { "dataTransfer": { "outToInternetGb": 450 } }
        }
      }
    }
    

    Enhanced fan-out retrieval lives on the AWS::Kinesis::StreamConsumer, not on the stream, so each registered consumer carries its own quantity. Global Accelerator takes independent inbound and outbound quantities per client geography; an undirected globalAccelerator.dataTransfer.gb reports as unknown rather than being priced at the cheaper direction. VPN outbound transfer shares the awsDataTransfer.outboundInternet pool with S3 egress, which is why one account pool covers both.

    The NAT gateway is the simplest entry here and often the most expensive: natGateway.processedGb is a single undirected number, because AWS bills every byte the gateway processes at one rate in either direction. It needs no account pool for the same reason.

    Layering defaults across an organization

    Precedence is what keeps a large repository's file small. Set the broad numbers once and override only the resources that differ.

    {
      "schemaVersion": 2,
      "revision": "2026-08-01",
      "accountContext": {
        "usage": {
          "lambda.requests": 120000000,
          "lambda.duration.x86": 900000
        }
      },
      "organizationDefaults": {
        "lambda": { "invocations": 250000, "averageDurationMs": 90 }
      },
      "repositoryDefaults": {
        "lambda": { "averageDurationMs": 140 }
      },
      "resourceTypeDefaults": {
        "AWS::Lambda::Function": {
          "lambda": { "invocations": 400000 }
        }
      },
      "resources": {
        "BatchStack/NightlyReport": {
          "lambda": { "invocations": 30, "averageDurationMs": 240000 }
        }
      }
    }
    

    Reading it back, narrowest layer wins per field: every function would start at 250,000 invocations from the organization layer, but the resource-type layer raises that to 400,000; the repository layer sets the average duration to 140 ms for all of them; and NightlyReport overrides both fields because it runs 30 times a month for four minutes. The report names the scope that supplied each quantity, so a reviewer can see at a glance which numbers were inherited and which were deliberate.

    Use stable addresses under resources: the full CDK construct path (BatchStack/NightlyReport) or the full Terraform address (module.batch.aws_lambda_function.nightly_report). Entries for resources outside the current diff are ignored, so one file can cover your whole repository. Wildcards are rejected, and a selector that matches more than one changed resource fails the whole block.

    A new account still on the free tier

    Free tier is opt-in because CloudBurn cannot see your billing console. Enable it, then state what is actually left this month.

    {
      "schemaVersion": 2,
      "revision": "2026-08-01",
      "freeTier": "enabled",
      "accountContext": {
        "usage": {
          "lambda.requests": 0,
          "lambda.duration.x86": 0,
          "lambda.duration.arm": 0,
          "apigateway.requests.http": 0,
          "s3.storage.standard": 0,
          "s3.requests.standardTier1": 0,
          "s3.requests.standardTier2": 0
        },
        "remainingFreeTier": {
          "lambda.requests": 850000,
          "lambda.duration": 310000,
          "apigateway.requests.http": 1000000,
          "s3.storage.standard": 5,
          "s3.requests.standardTier1": 2000,
          "s3.requests.standardTier2": 20000
        }
      },
      "resources": {
        "ApiStack/OrdersHandler": {
          "lambda": { "invocations": 200000, "averageDurationMs": 150 }
        },
        "ApiStack/PublicApi": {
          "apigateway": { "requests": 200000 }
        },
        "StorageStack/UploadsBucket": {
          "s3": { "storage": { "standardGbMonth": 3 }, "requests": { "tier1": 1500, "tier2": 9000 } }
        }
      }
    }
    

    Note the two lambda.duration.* entries under usage against the single lambda.duration entry under remainingFreeTier. That is not a typo: the billing pools split per architecture while the free allowance does not, and the free tier reference lists which pool name each side takes. If you enable free tier but leave out a pool an eligible component needs, that component reports as unknown rather than silently pricing at full rate.

    You can also enable free tier for one scope only:

    {
      "schemaVersion": 2,
      "freeTier": "disabled",
      "accountContext": {
        "usage": { "s3.storage.standard": 0 },
        "remainingFreeTier": { "s3.storage.standard": 5 }
      },
      "resources": {
        "SandboxStack/ScratchBucket": {
          "freeTier": "enabled",
          "s3": { "storage": { "standardGbMonth": 2 } }
        }
      }
    }
    

    Prove the config, then keep it honest

    The cost report is the feedback loop. Open a pull request and read it before trusting the file.

    • An unknown component names the exact input it is missing — an assumption field, accountContext.usage.<pool>, or accountContext.remainingFreeTier.<pool>. Add what it names and the component becomes assumed on the next push.
    • An assumed component names the value, unit, scope, and field that supplied it. If the scope surprises you, a broader layer is winning that you forgot about.
    • A report with any unknown component labels its subtotal as excluding unknowns and never uses no-cost wording, so an incomplete file is visible rather than quietly optimistic.

    To keep the numbers from rotting:

    • Bump revision on every edit. It is a free label, and it makes "when did this estimate change and why" answerable from the diff alone.
    • Re-measure quarterly, or after any launch that changes traffic shape. Assumptions age faster than infrastructure does.
    • Write explicit zeros. 0 means "this workload genuinely does none of that." Omitting the field means "nobody has looked yet," and the report renders the two differently.
    • Review assumption edits like code. Because both snapshots are priced, a pull request that only changes assumptions shows its own cost delta — which is exactly when a wrong number is cheapest to catch.

    What's next

    Read thisTo learn
    Usage assumptions schemaEvery field, unit, pool, and limit in full
    GitHub App overviewHow the app turns diff comments into cost reports
    Set up PR cost estimationInstalling and linking the app from your dashboard
    ← PreviousUsage AssumptionsNext →Cost Optimization

    CloudBurn Platform · early 2027

    Price changes before you deploy. Fix the waste after.

    Join now for 30% off, for as long as you keep your plan.

    Join the waitlist

    Two emails a month at most. Unsubscribe anytime. By signing up you agree to the privacy policy.