Skip to main content

bicep-cost

Estimate the monthly Azure cost of a Bicep (or compiled ARM) deployment from the command line, using the public Azure Retail Prices API (the same list prices the Azure pricing calculator uses). No Azure login needed.

uv tool install .            # or: uvx --from . bicep-cost ...

bicep-cost estimate app/infra/main.bicep --set administratorLoginPassword=x
bicep-cost estimate other-app/infra/parameters/prod.bicepparam          # uses the file's `using` template
bicep-cost estimate main.bicep -p parameters/prod.bicepparam --currency CHF --detail
bicep-cost estimate compiled.json -p main.parameters.json --format markdown   # azd ${VAR} placeholders read from env
RESOURCE                                      FIXED        USAGE*         TOTAL
serverfarms: app-plan                     259.88          0.00        259.88
containerApps: app-api                    217.28          0.00        217.28
flexibleServers: app-postgres             152.57          0.00        152.57
...
Provisioned (fixed):                869.11 USD
Usage-based* (assumed):              43.91 USD
TOTAL per month:                    913.02 USD

How it works

  1. .bicep / .bicepparam are compiled with the Bicep CLI ($BICEP_CLI, bicep on PATH, ~/.azure/bin/bicep, or az bicep). A compiled ARM .json skips this step.
  2. A small ARM evaluator expands the template: parameters, variables, condition, copy loops, nested deployments (Bicep modules) and their outputs. Values that only exist after deployment (reference() of live resources, listKeys(), unset parameters) become unknown and never silently turn into guesses.
  3. One estimator per resource type turns resources into meter quantities; prices are looked up in the Retail Prices API (cached in ~/.cache/bicep-cost, 7 days) and tiers / free grants are applied per subscription or workspace.

Fixed vs. usage-based

Every line is either fixed (follows from the template: plan SKU x instances, database compute, always-on replicas, private endpoint hours, alert rules, cron-scheduled replicas) or usage-based* (depends on traffic or data volume: storage GB and operations, log ingestion, session hours, function executions, AI tokens). Without measurements, usage-based numbers come from small built-in defaults (e.g. 10 GB per storage account, 2 GB of logs) - good enough to see the shape, not to quote. --detail lists every assumption used and the report says how many figures are still defaults.

Measure the real values from a deployed environment

az login
bicep-cost collect app/infra/main.bicep --set administratorLoginPassword=x \
    --resource-group my-resource-group                 # -> main.usage.toml (next to the template)
bicep-cost collect other-app/infra/main.bicep -p other-app/infra/parameters/prod.bicepparam \
    --resource-group <rg> --days 60                            # -> parameters/prod.usage.toml

bicep-cost estimate app/infra/main.bicep ...                 # picks main.usage.toml up automatically

collect is read-only (GETs, Azure Monitor metric reads and two read-only POST queries: Log Analytics Usage and Cost Management query). It evaluates the template with the real subscription and resource group so that resource ids match the deployed ones, then writes a commented usage file:

Resource Measured from Usage key
Storage account blob service BlobCapacity, Transactions by ApiName (read / write / other) stored_gb, *_operations_10k
File share FileCapacity, Transactions (account total split over its shares on standard accounts) used_gb, *_operations_10k
Log Analytics Usage table (billable GB) + top tables; App Insights in the same workspace is set to 0 ingest_gb_per_month
Container App Replicas (hourly), UsageNanoCores/RxBytes/TxBytes (5-min idle share), Requests replica_hours, idle_percent, requests_millions
Session pool, AI/Foundry account Cost Management actual cost (pool metrics do not reflect billed session-hours) monthly_cost
Function app (Consumption/Flex) FunctionExecutionCount, FunctionExecutionUnits executions_per_month, gb_seconds_per_month
Key Vault / private endpoint / private DNS zone / Service Bus ServiceApiHit / PEBytesIn/Out / QueryVolume / message counts operations_10k / *_gb / queries_millions / operations_millions
App Service plan, PostgreSQL live resource: instance count, storageSizeGB (auto-grow) instances, storage_gb

It also prints drift (template says 32 GB / 2 instances, live says 64 GB / 3) and each resource's actual cost of the window as a comment, so you can sanity-check the estimate. Files are merged key by key (--usage a.toml --usage b.toml, later wins), so hand-written overrides can live in a separate file. bicep-cost init-usage main.bicep writes a file with every tunable value and its default instead.

Usage file lookup for estimate: explicit --usage (repeatable), else bicep-cost.usage.toml and <params-or-template stem>.usage.toml next to the input. Tables match the resource name, exact or glob ([storage."stprod*"]); see examples/usage.toml.

Caveats of the measurements: windows are scaled to a month (730 h); idle_percent is judged on 5-minute windows and can be lower than what is billed; Container Apps free grants are subscription-wide, so if other apps in the subscription use them up, the estimate understates; monthly_cost values are in the billing currency recorded as [meta] cost_currency (the estimate then defaults to that currency, no conversion happens).

Negotiated discounts

List prices are what the API returns. If you pay less (EA/MCA discounts, reservations), say so per resource type:

[discounts]
"Microsoft.Web/serverfarms" = 30        # percent off list
"Microsoft.DBforPostgreSQL/flexibleServers" = 30
default = 0

Supported resource types and tiers

Area Types Tiers / SKUs covered
App Service serverfarms, sites, slots Free; Basic B1-3; Standard S1-3; Premium v2/v3/v4 (incl. P1mv3); Isolated v2 (stamp fee not included); Linux and Windows. Sites/slots ride on the plan; sites on plans outside the template are reported as external
Functions / Logic Apps serverfarms + sites Consumption (Y1), Flex (FC1: always-ready + on-demand), Elastic Premium EP1-3, Logic Apps Standard WS1-3
Container Apps managedEnvironments, containerApps, sessionPools Consumption profile (active/idle rates, free grants, KEDA cron schedules), dedicated profiles D4-D32 / E4-E32 (+ plan management fee), dynamic sessions (Python/Node) and custom-container pools
Storage storageAccounts, file shares LRS / ZRS / GRS / RA-GRS / GZRS / RA-GZRS; Hot / Cool / Cold; Blob, ADLS Gen2 (HNS), standard file shares (TransactionOptimized / Hot / Cool). Not Premium
PostgreSQL flexibleServers Burstable, General Purpose, Memory Optimized (any series); HA doubles compute + storage; Premium SSD v2 IOPS / throughput; backup storage above the free allowance (geo-redundant aware)
Azure SQL servers/databases, servers/elasticPools, managedInstances vCore: General Purpose (provisioned and serverless), Business Critical, Hyperscale (+ HA replicas), zone redundancy, Gen5 / DC / Fsv2 / M families; DTU: Basic, Standard S0-S12, Premium P1-P15; DTU elastic pools; Managed Instance GP / BC. Databases in a pool are included. Usage keys use the full name server/database. Azure Hybrid Benefit is not applied (price shown includes the license). Not Synapse dedicated pools, LTR backups
Virtual machines virtualMachines, virtualMachineScaleSets, disks, publicIPAddresses any VM size (by armSkuName), Linux / Windows / Hybrid Benefit, Spot; OS + data disks (Premium / Standard SSD / Standard HDD, LRS / ZRS, tier from size); ephemeral OS disks free; VMSS = instances x VM (set instances for autoscale). Not Ultra / Premium SSD v2 disks, SQL / RHEL / SUSE license surcharges, load balancers, gateways
Service Bus namespaces Basic, Standard (base fee + tiered operations, first 13 M included), Premium (1/2/4/8/16 messaging units)
Monitoring workspaces, components, scheduledQueryRules PerGB2018 (free 5 GB, tiers), commitment tiers, retention > 31 d. Not Basic/Auxiliary table plans
Key Vault vaults Standard / Premium operations. Not HSM keys or managed HSM
Network privateEndpoints, privateDnsZones per-hour + data processed; zone tiers (first 25 zones). Not gateways, firewalls, public IPs
AI CognitiveServices/accounts no base fee; spend via monthly_cost (measured by collect). Not provisioned throughput (PTU)
Free (explicitly known) role assignments, VNets/subnets, DNS records and links, budgets, dashboards, diagnostic settings, secrets, access policies, databases/configs, blob containers, queues/topics, ...

Anything else is listed under Not covered by this tool (cost NOT included); use --strict to fail in that case. Premium storage, ASE stamps, bandwidth/egress and reservations are not modelled; prices are pay-as-you-go list prices unless you add [discounts] or opt into account price sheets (below).

Add a resource type by writing estimate_x(res, ctx) -> ResourceEstimate in bicep_cost/estimators/ and registering it in estimators/__init__.py.

Other commands and flags

bicep-cost estimate ... --format json -o head.json     # also: markdown (PR comments), csv
bicep-cost diff base.json head.json                    # per-resource cost change between two reports
bicep-cost estimate ... --fail-above 1500              # exit 3 above a monthly budget (CI guard rail)
bicep-cost estimate ... --strict                       # exit 2 if something could not be priced
bicep-cost estimate ... --group-by type|module         # summary table instead of per-resource rows
bicep-cost prices --service "Azure Container Apps" --meter-contains Idle   # browse meters

Other options: --location (default region and resourceGroup().location, default westeurope), --resource-group, --subscription-id, --offline / --refresh / --no-cache, -v (log API calls).

Development

uv sync --group test --group dev
uv run -m pytest
uv run ruff check . && uv run ruff format --check .
uv run ty check --error-on-warning

Currency

Estimates use USD by default. Set --currency CHF (or EUR, for example) on estimate and prices to request that currency directly from Azure:

bicep-cost estimate main.bicep --currency CHF
bicep-cost prices --service "Key Vault" --currency EUR

For estimates the precedence is --currency, then [meta] cost_currency in the usage TOML, then USD. Codes are case insensitive. Reports and budget limits use the selected currency; cached prices are isolated by currency. Manual monthly_cost values must use the same currency as the estimate; conflicting usage-file metadata is rejected. No exchange-rate conversion is performed. Diffs require both reports to use the same currency.

Azure's non-USD retail rates are budget estimates, not negotiated billing rates. See Microsoft's Retail Prices API currency documentation.

Percentage cost caps in CI

Save a baseline estimate (using the same currency and usage assumptions), then fail if the new monthly cost grows by more than 10 percent:

bicep-cost estimate main.bicep --format json -o base.json
# After changing the template:
bicep-cost estimate main.bicep --baseline base.json --fail-increase-percent 10 --fail-above 1500
# Or compare two saved JSON reports:
bicep-cost diff base.json head.json --fail-increase-percent 10 --fail-above 1500

Either exceeded cap returns 3. Equality and decreases pass; any positive cost from a zero baseline fails the relative cap. Thresholds must be finite and nonnegative. The full report is written before a budget failure, including with --output, so CI can always retain it. --strict failures still take precedence (exit 2). Baseline reports must use the same currency; no conversion is applied.

Cost breakdown charts

Text and Markdown estimates include horizontal bars ordered from the most expensive resource to the least. Each bar shows the monthly amount and share of the known total. Text --group-by type or --group-by module aggregates both the chart and table. Zero-cost resources do not get bars; unresolved and unsupported resources remain listed below, since their costs are not included in the total. Charts are plain text for readable CI logs and Markdown PR comments, and are written even when a budget cap fails. JSON and CSV remain machine-readable.

Automatic releases

Like bmsui, main pushes run reusable CI, detect a new package version, publish to PyPI using OIDC, and create a GitHub release with wheel/sdist assets. PR CI checks Ruff, ty, tests, builds and an installed-wheel smoke test on Python 3.11 and 3.14.

Before the first merge, a PyPI maintainer must register a pending trusted publisher: project bicep-cost, owner bmsuisse, repository bicep-cost, workflow release.yml, environment pypi. Use the same environment in GitHub; restrict its deployment branches to main. No long-lived PyPI token is needed.

Bump [project] version in pyproject.toml, run uv lock, and merge the PR. Main publishes that version after CI passes; an existing GitHub release is skipped. The Release workflow can also be dispatched manually from main; its dry_run input defaults to true, validating without publishing. Release jobs are serialized. If publication succeeded but release creation failed, rerun the workflow: existing PyPI files are skipped and GitHub release creation is retried. Do not change files for a version already uploaded to PyPI; bump the version.

Azure account prices

Retail prices remain the default and require no login. To use negotiated consumption rates through the logged-in Azure CLI user:

az login
bicep-cost estimate main.bicep --azure-prices --subscription-id YOUR_SUBSCRIPTION_ID
# Persist a private snapshot while estimating:
bicep-cost estimate main.bicep --azure-prices --save-price-sheet account-prices.json
# Or download separately:
bicep-cost price-sheet --subscription-id YOUR_SUBSCRIPTION_ID -o account-prices.json
# Reuse without Azure login:
bicep-cost estimate main.bicep --price-sheet account-prices.json

Without a subscription argument, downloads use the CLI's current subscription. The read-only Consumption Price Sheet API requires access to the subscription's pricing and a supported billing agreement. An ordinary Azure login alone does not guarantee access. Unsupported agreements, missing sheets and denied access produce an error. Within a successfully loaded price sheet, missing meter rates fall back to retail prices with warnings.

For MCA/MPA accounts, or when subscription downloads are unavailable, download and extract the current consumption price-sheet CSV from Cost Management + Billing, then use --price-sheet prices.csv. The importer accepts case-insensitive column names meterId, optional skuId, currencyCode or currency, unitOfMeasure, unitPrice, optional tierMinimumUnits and priceType. It uses price currency, not billingCurrency; reservations and savings-plan rows are excluded. Import a single current price period: conflicting duplicate rates are rejected. Legacy CSV layouts must be converted to these columns first. Included-quantity grants are rejected because the sheet does not supply enough billing-scope information to allocate them safely.

Account mode discovers meter identities through the Retail API, then replaces rates by meter/SKU ID, normalizing compatible block units (for example 100 Hours to 1 Hour). Missing account meter rates use retail prices with an explicit pricing warning in text, Markdown, and JSON reports; --strict does not block this fallback. Resources with no retail meter still become unresolved. Manual [discounts] are ignored because account rates already include discounts. Reports record the sheet's origin and snapshot time. Explicit --currency and usage-file currency must match the sheet; by default its currency is selected. No exchange-rate conversion is performed.

Snapshots contain negotiated rates, not Azure tokens, and are saved atomically with owner-only permissions. Keep them private and out of source control. Saved sheets can be used without login; completely offline estimates also need the Retail API metadata in the normal price cache. Use --price-sheet FILE --offline for that; --azure-prices always downloads fresh rates. Snapshots are not refreshed automatically, so download a new sheet when your agreement or billing period changes.

Quality gates

Ruff uses the strict BMS Python rule families from init-app-stack (annotations, modern syntax, imports, bugbear, performance, pathlib, naming and logging). The target stays Python 3.11, matching package support. FastAPI-only rules do not apply to this CLI; CLI printing is allowed specifically in cli.py and the file-size guard. Relative imports are banned. ANN401 (explicit Any) is a nonblocking warning, matching the BMS scaffold. Production and pytest function signatures are annotated; no test-wide annotation exception is used.

Ty checks fail on warnings, with possibly missing attributes/imports and missing generic type arguments treated as errors. Default type diagnostics remain active; optional experimental rules are not enabled indiscriminately. CI runs the same checks and formatting verification on Python 3.11 and 3.14.

uv sync --locked --all-groups
uv run prek install             # enable the pre-commit hook
uv run prek run --all-files      # format, lint, typecheck, YAML and file-size checks

The hooks use the repository's locked tool versions. Re-run after automatic fixes and stage the resulting changes before committing. Pytest remains a CI check; run focused tests locally for behavior you change.

Metadata

Release files for bicep-cost 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for bicep-cost 0.1.1
File Size Uploaded Provenance
bicep_cost-0.1.1.tar.gz 101.5 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for bicep-cost 0.1.1
File Interpreter ABI Platform
bicep_cost-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 185.0 kB

Release files / bicep_cost-0.1.1.tar.gz

Download URL bicep_cost-0.1.1.tar.gz
Size 101.5 kB
Tags Source
SHA-256 checksum
How to use checksums
bb7f7cbf15223cad984fce314154575356e4ed2cf68ea7597f3cd4876577fb9a
BLAKE2b-256 checksum
How to use checksums
3ef000723bb707ffa1f3976df7d8badb3abc4ede38f1cf7351841fba59693409
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

Release files / bicep_cost-0.1.1-py3-none-any.whl

Download URL bicep_cost-0.1.1-py3-none-any.whl
Size 83.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e83622702dc5a5450bab8ae2a9e600572c61fa556669a9c4a3402bb7b4ce14f5
BLAKE2b-256 checksum
How to use checksums
a55d97e766952ce0cda0529e96f438581be546b78ac88c5b3ec01be90bf68ceb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

Security for bicep-cost 0.1.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for bicep_cost-0.1.1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page