cost
Show a per-service cost breakdown for an app: integrated GPU / CPU / memory hours, instance type, and month-to-date spend.
Usage
convox cost [-a app] [--aggregate] [--start YYYY-MM-DD] [--end YYYY-MM-DD] [--format table|json]
--aggregate switches the table to a single row of app-level totals.
--start / --end bound the snapshot to a calendar window; rows whose
AsOf falls outside the window are zeroed. --format json emits the raw
*structs.AppCost for jq consumption.
Examples
Default per-service / per-variant breakdown on a 3.24.6+ rack:
$ convox cost --app myapp
SERVICE INSTANCE CAPACITY ACTIVE-REPLICAS SPEND-USD
vllm g4dn.xlarge on-demand 3 $0.30
api t3.medium spot 2 $0.08
worker t3.small spot 1 $0.04
_build c5.large on-demand — $0.02
_unattributed t3.medium on-demand — $0.01
TOTAL: $0.45
Cost accumulates per (instance-type, capacity-type) combination across the month. A row may show 0 active replicas if pods previously ran on that variant but have since migrated or been removed.
Spot pricing applies a discount automatically when nodes are provisioned via Karpenter, an EKS spot ASG, or an AKS spot node pool. Capacity "unknown" means the node carried no capacity label.
Aggregated app totals via --aggregate:
$ convox cost --app myapp --aggregate
APP SPEND-USD AS-OF PRICING-SOURCE
myapp $0.45 2 minutes ago pricing-table:2026-07-14
Output table (3.24.6+)
The default 3.24.6 output is one row per (service, instance-type, capacity-type) triple. Column-position contract:
| Position | Column | Description |
|---|---|---|
| 1 | SERVICE |
Service name (_build / _unattributed for reserved buckets). |
| 2 | INSTANCE |
Instance type as labeled on the node (e.g. g4dn.xlarge). |
| 3 | CAPACITY |
on-demand, spot, or unknown (verbatim from the node label). |
| 4 | ACTIVE-REPLICAS |
Pod count on this variant at the most recent tick; — if 0. |
| 5 | SPEND-USD |
Accumulated spend for this variant in the current billing month. |
Sort order is descending by SPEND-USD with alphabetical secondary
tiebreak. Each row is one entry in the underlying
AppCost.variant-breakdown array (structs.ServiceVariantCostLine).
Two reserved buckets may appear alongside service rows:
_build: build pods (carryservice-type=build) attributed away from the service they are building so normal-operation cost stays uninflated._unattributed: pods without aservicelabel (system sidecars, KEDA scalers, anything not user-deployed).
Pre-3.24.6 racks (fallback table)
Older racks (3.24.5 and earlier) emit no variant-breakdown array. The
CLI auto-falls-back to the legacy aggregated columns:
SERVICE GPU-HOURS CPU-HOURS MEM-GB-HOURS INSTANCE SPEND-USD
vllm 0.00 0.00 0.00 g4dn.xlarge $0.30
GPU-HOURS / CPU-HOURS / MEM-GB-HOURS are reserved for a future
per-resource pricing model. They render 0.00 on every release that
ships this column shape. The SPEND-USD column is populated from the
accumulator's per-service totals.
JSON output shape
convox cost --format json emits the raw *structs.AppCost for jq
consumption. On 3.24.6+ racks the response carries BOTH a breakdown
array (legacy aggregated rows) and a variant-breakdown array (one row
per (service, instance-type, capacity-type) triple). The variant
schema:
{
"service": "vllm",
"instance-type": "g4dn.xlarge",
"capacity-type": "on-demand",
"spend-usd": 0.30,
"replicas": 3
}
Pre-3.24.6 racks omit variant-breakdown entirely (the field is
omitempty) and emit only the legacy breakdown array. Stable-shape
consumers should fail-open on a missing variant-breakdown and prefer
that array when present.
See Per-service cost breakdown for bucket semantics, the 1000-entry truncation cap, and the service-rename / deleted-service / downgrade behavior.
The breakdown populates from the first accumulator tick after rack upgrade to 3.24.6 (default tick interval is 10 minutes); pre-upgrade history is not retroactively attributed.
Cost tracking prerequisite
The convox cost read path always returns 200. When cost_tracking_enable
is false, the response is a zero SpendUsd and an empty breakdown rather
than an error, so dashboards and scripts polling the endpoint do not break.
Spend only populates after the rack parameter is set:
$ convox rack params set cost_tracking_enable=true
# wait ~3 min for apply, then the next accumulator tick (~10 min default)
# starts populating spend.
The 422 rejection applies only to the WRITE paths: convox budget set and
convox deploy against a manifest with a budget: block. See
Cost tracking prerequisite
for the full enable instructions. Cost tracking is available on AWS racks
(3.24.6+) and Azure racks (3.25.1+).
Unpriced instance types
When a pod runs on an instance type the rack's price table does not know
about (a brand-new family, a custom Karpenter NodePool, an exotic GPU SKU on
metal), the row shows ? or 0.00 for the cost columns. The pod still
runs; only the cost-tracking column is blank. See Unpriced instance
types for the diagnostic
recipe and workaround.
Pricing adjustment
The pricingAdjustment field in convox.yml is applied multiplicatively at
sample time. A value of 1.10 produces 10% more recorded spend than the raw
price would; 0.95 produces 5% less. Use this to align Convox's internal
pricing with the contract pricing your finance team sees, or to add buffer
for cap headroom.
Per-month rollover
Month-to-date spend resets to zero at the first of each month, UTC. Caps that were tripped in the previous month are cleared as part of the rollover.
See Also
- Cost Tracking: operational guide
- Budget Caps: caps that consume the spend signal
- convox.yml budget block: schema reference