Configuration

Every setting for vBilling v0.2: environment variables, Helm chart values, and pricing setup in your billing backend.

Environment Variables

vBilling reads its configuration from environment variables; the Helm chart sets them from values.yaml. Credentials come from Kubernetes Secrets (see existingSecret in each section of the chart).

Destinations

VariableDescriptionDefault
ADAPTERS Comma-separated destinations usage fans out to: stripe, metronome, lago, webhook, noop. Each destination reads the ledger through its own cursor. lago
ADAPTER Deprecated v0.1 single-adapter form, used only when ADAPTERS is empty. (none)

Identity and region

VariableDescriptionDefault
REGION Region stamped on every event (for example ap-southeast-2). Run one vBilling per control plane cluster. default
CLUSTER_NAME Name of this control plane cluster; used as the CloudEvents source. control-plane
ZONE Default zone when nodes carry no topology.kubernetes.io/zone label. (none)
REGION_FROM_NODE Use the node's topology.kubernetes.io/region label instead of REGION. false
DEFAULT_TENANT_CLASS Tenant class stamped on events when a tenant cluster has none (public, enterprise, government, dev, ...). (none)
TENANT_SOURCE cluster: one customer per tenant cluster. project: one per vCluster Platform project. The vbilling.vcluster.com/tenant label always wins. cluster
PLATFORM_CLUSTER vCluster Platform cluster name of this control plane cluster, so Platform enrichment only applies to local tenant clusters. (none)
WATCH_NAMESPACES Comma-separated namespaces to discover tenant clusters in. Empty watches all namespaces. (all)

Ledger

VariableDescriptionDefault
DATA_DIR Directory for the ledger and adapter state. Keep it on a persistent volume. /var/lib/vbilling
RETENTION How long closed ledger segments are kept once every destination has consumed them. 168h

Metering

VariableDescriptionDefault
COLLECTION_INTERVAL Metering window size, aligned to the wall clock. Must be 10s to 1h and divide an hour. 60s
RECONCILE_INTERVAL How often tenant clusters are rediscovered. 30s
MAX_BACKFILL Windows missed during downtime are backfilled up to this age. 6h
OFFBOARD_GRACE A tenant must have no tenant clusters for this long before destinations offboard it. 1h
CPU_MEMORY_BASIS usage (metrics-server), requests, or max of both. usage
METER_CONTROL_PLANE Also bill the tenant cluster's own control plane pods and data PVCs (normally covered by instance hours). false
METER_BY_NAMESPACE Add the namespace inside the tenant cluster as a dimension, for tenant-side chargeback. false
GPU_RESOURCES Full-GPU resource names. MIG (nvidia.com/mig-*) and nvidia.com/gpu.shared are always detected. nvidia.com/gpu,amd.com/gpu
SKU_LABEL Node label whose value is the SKU (for example vbilling.vcluster.com/sku). (none)
CAPACITY_TYPE_LABEL Node label for on-demand, spot, preemptible or reserved; beats cloud-provider labels. vbilling.vcluster.com/capacity-type
UNHEALTHY_NODE_TAINTS Taints that mark a node down: its GPU time is recorded as downtime, not billed. Add your GPU health taints. node.kubernetes.io/not-ready,node.kubernetes.io/unreachable
VCLUSTER_NODE_LABEL Extra dedicated-node selector, key=%s where %s is the tenant cluster name. See Dedicated nodes. (none)
CUSTOM_METRICS Operator metrics accepted by the ingest API, code:unit[:key|key],... (none)
PROMETHEUS_URL Any Prometheus HTTP API (Prometheus, Thanos, Mimir, VictoriaMetrics). Enables egress and GPU utilization metrics. (none)
EGRESS_QUERY PromQL template for egress bytes. Placeholders: {{namespace}}, {{vcluster}}, {{instance}}, {{project}}, {{tenant}}, {{tenant_cluster}}, {{window}} (e.g. 60s), {{window_seconds}} (e.g. 60). container_network_transmit_bytes_total increase
GPU_UTIL_QUERY, GPU_COUNT_QUERY Replace the GPU utilization queries (same placeholders, results grouped by modelName or gpu_type). none disables utilization. DCGM, namespace or exported_namespace
PROMETHEUS_HEADERS Extra query headers, Name=value,..., e.g. X-Scope-OrgID for Mimir or Authorization. Basic auth also works in the URL. (none)
PROMETHEUS_PRESET vcluster-platform reads vCluster Platform fleet observability: egress and GPU utilization selected by the vcluster_platform_instance and vcluster_platform_project labels. (none)
PROMETHEUS_METRICS_FILE Metrics defined by PromQL, evaluated per tenant cluster at every window end (billable ones are created in the billing backends). (none)
PROMETHEUS_BEARER_TOKEN_FILE Bearer token file, re-read on every query (for example a service account token for an OpenShift Thanos querier). (none)

Tenant clusters with their own nodes

VariableDescriptionDefault
TENANT_APIMeter tenant clusters with their own nodes (Private Nodes, Auto Nodes, Standalone) through their own API.true
TENANT_KUBECONFIG_SECRETSecret in each tenant cluster's namespace holding a read-only kubeconfig (vCluster exportKubeConfig.additionalSecrets).vbilling-kubeconfig
TENANT_ADMIN_FALLBACKAlso accept the admin kubeconfig vc-<name> (needs Secret read access).false
TENANT_CLUSTERS_FILEYAML list of external tenant clusters: name, kubeconfig, tenant, displayName, project, metadata.(none)
PRIVATE_NODE_BILLINGnode bills each private node whole; usage bills the pods on them.node
TENANT_EXCLUDE_NAMESPACESUsage mode: namespaces never billed.kube-system
WATCH_DELETIONSBill pods and nodes deleted between windows until the moment they were deleted.true
DRA_GPU_DRIVERSDRA drivers whose devices are GPUs (ResourceClaim metering).gpu.nvidia.com,gpu.amd.com

API and enforcement

VariableDescriptionDefault
LISTEN_ADDR HTTP listen address for the API, dashboard, metrics and webhooks. :8080
API_TOKEN Bearer token for /api/*. Empty leaves the API unauthenticated (keep the Service internal). (none)
INGEST_TOKEN Bearer token for POST /api/v1/events; defaults to API_TOKEN. Ingest is disabled without a token. (none)
ENFORCEMENT_MODE observe, annotate or enforce. See the README. observe
ENFORCEMENT_RULES Overrides, event.type=state,... (for example alerts.spend_threshold_reached=suspended). (none)

Stripe

VariableDescriptionDefault
STRIPE_API_KEY Secret or restricted key. Test-mode keys default to 20 requests/s. (none)
STRIPE_WEBHOOK_SECRET Enables /webhooks/stripe (dunning and usage alerts). (none)
STRIPE_SPLIT_METERS_BY Event fields that get their own meter (Stripe meters cannot price on dimensions). sku
STRIPE_AUTO_SUBSCRIBE Subscribe tenants to active metered prices tagged metadata[vbilling_plan]=<plan>. false
STRIPE_MAX_RPS Request rate cap; 0 picks 20 (test) or 200 (live). 0
STRIPE_CANCEL_ON_REMOVE Cancel the tenant's subscription at period end when it is offboarded. false
STRIPE_API_VERSION Pinned Stripe API version. 2026-09-30.endive
STRIPE_API_BASE API base URL (stripe-mock for tests). https://api.stripe.com

Metronome

VariableDescriptionDefault
METRONOME_API_TOKEN Bearer token. (none)
METRONOME_WEBHOOK_SECRET Enables /webhooks/metronome (spend, usage and credit alerts). (none)
METRONOME_RATE_CARD Rate card alias or ID; creates a contract per tenant. A tenant's plan annotation overrides it. (none)
METRONOME_STRIPE_LINK Create each tenant's Stripe customer and link it for invoicing (needs STRIPE_API_KEY). false
METRONOME_STRIPE_COLLECTION_METHOD charge_automatically or send_invoice. charge_automatically
METRONOME_API_BASE API base URL. https://api.metronome.com

Lago

VariableDescriptionDefault
LAGO_API_URL Lago API base URL. http://localhost:3000
LAGO_API_KEY Required when the lago adapter is enabled. (none)
DEFAULT_PLAN_CODE Plan code for new tenants (Lago plan; Stripe vbilling_plan tag). vcluster-standard
BILLING_CURRENCY ISO 4217 currency for customers and plans. USD

Webhook

VariableDescriptionDefault
WEBHOOK_URL Endpoint receiving CloudEvents 1.0 batches. (none)
WEBHOOK_SECRET HMAC secret for the X-VBilling-Signature header (Stripe-compatible scheme). (none)
WEBHOOK_HEADERS Extra headers, k=v,k=v. (none)

Deprecated

VariableDescriptionDefault
SPOT_DISCOUNT_PERCENT Ignored since v0.2. Price capacity_type in your billing backend instead, so quantities stay physical and reconcilable. (none)

Helm Chart Values

The chart deploys a StatefulSet with a persistent ledger volume, a Service, RBAC (enforcement verbs only outside observe mode), and optionally a ValidatingAdmissionPolicy and ServiceMonitor.

ValueSetsDefault
adapters ADAPTERS [lago]
region / clusterName REGION / CLUSTER_NAME default / control-plane
tenantClass / tenantSource DEFAULT_TENANT_CLASS / TENANT_SOURCE "" / cluster
billing.collectionInterval / reconcileInterval / maxBackfill / offboardGrace COLLECTION_INTERVAL, RECONCILE_INTERVAL, MAX_BACKFILL, OFFBOARD_GRACE 60s / 30s / 6h / 1h
billing.defaultPlanCode / currency DEFAULT_PLAN_CODE / BILLING_CURRENCY vcluster-standard / USD
metering.* CPU_MEMORY_BASIS, METER_*, GPU_RESOURCES, SKU_LABEL, CAPACITY_TYPE_LABEL, UNHEALTHY_NODE_TAINTS, VCLUSTER_NODE_LABEL, REGION_FROM_NODE, CUSTOM_METRICS see above
prometheus.url / egressQuery / gpuUtilQuery / gpuCountQuery / headers / existingSecret / bearerTokenFile / preset / metrics PROMETHEUS_URL / EGRESS_QUERY / GPU_UTIL_QUERY / GPU_COUNT_QUERY / PROMETHEUS_HEADERS (chart Secret) / PROMETHEUS_BEARER_TOKEN_FILE / PROMETHEUS_PRESET / PROMETHEUS_METRICS_FILE (ConfigMap) (none)
tenantAPI.enabled / secretName / adminFallback / privateNodeBilling / excludeNamespaces / externalClustersSecret TENANT_API / TENANT_KUBECONFIG_SECRET / TENANT_ADMIN_FALLBACK / PRIVATE_NODE_BILLING / TENANT_EXCLUDE_NAMESPACES / TENANT_CLUSTERS_FILE (Secret mounted at /etc/vbilling/tenants) true / vbilling-kubeconfig / false / node / [kube-system] / ""
metering.watchDeletions / draGPUDrivers WATCH_DELETIONS / DRA_GPU_DRIVERS true / [gpu.nvidia.com, gpu.amd.com]
platform.cluster PLATFORM_CLUSTER (none)
lago.apiURL / apiKey / existingSecret (key api-key) LAGO_API_URL / LAGO_API_KEY (none)
stripe.apiKey / webhookSecret / existingSecret (keys api-key, webhook-secret) STRIPE_API_KEY / STRIPE_WEBHOOK_SECRET (none)
stripe.splitMetersBy / autoSubscribe / maxRPS / cancelOnRemove / apiVersion / apiBase STRIPE_* [sku] / false / 0 / false
metronome.apiToken / webhookSecret / existingSecret (keys api-token, webhook-secret) METRONOME_API_TOKEN / METRONOME_WEBHOOK_SECRET (none)
metronome.rateCard / stripeLink / stripeCollectionMethod / apiBase METRONOME_* (none)
webhook.url / secret / headers / existingSecret (key secret) WEBHOOK_* (none)
api.port / token / ingestToken / existingSecret (keys api-token, ingest-token) LISTEN_ADDR / API_TOKEN / INGEST_TOKEN 8080
api.service.type / port Service ClusterIP / 8080
enforcement.mode / rules ENFORCEMENT_MODE / ENFORCEMENT_RULES observe
enforcement.admissionPolicy.enabled ValidatingAdmissionPolicy blocking new pods in suspended namespaces (Kubernetes 1.30+) false
persistence.enabled / size / storageClass / retention Ledger PVC (StatefulSet volumeClaimTemplate) / RETENTION true / 5Gi / "" / 168h
serviceMonitor.enabled / interval Prometheus Operator ServiceMonitor false / 30s
image.repository / tag / pullPolicy Container image ghcr.io/vclusterlabs-experiments/vbilling / v0.2.0

Example Helm install

helm upgrade --install vbilling deploy/helm/vbilling \ -n vbilling-system --create-namespace \ --set adapters='{metronome,webhook}' \ --set metronome.existingSecret=metronome-credentials \ --set metronome.rateCard=payg-aud --set metronome.stripeLink=true \ --set stripe.existingSecret=stripe-credentials \ --set webhook.url=https://data.example.com/usage \ --set region=ap-southeast-2 --set api.existingSecret=vbilling-api

Lago Pricing Configuration

vBilling bootstraps 9 billable metrics and a vcluster-standard plan on startup. All charges default to $0.00 per unit. You must configure pricing in the Lago UI or API to actually generate non-zero invoices.

Charges to configure

In the Lago UI, go to Plans > vCluster Standard > Edit and set the per-unit price for each charge:

Metric CodeAggregation FieldCharge ModelSuggested Price
vcluster_cpu_core_hours cpu_core_hours Standard (per unit) $0.05
vcluster_memory_gb_hours memory_gb_hours Standard (per unit) $0.01
vcluster_storage_gb_hours storage_gb_hours Standard (per unit) $0.001
vcluster_instance_hours instance_hours Standard (per unit) $0.10
vcluster_gpu_hours gpu_hours Standard (per unit) $2.50
vcluster_gpu_utilization gpu_util_score Standard (per unit) $0.00 (informational)
vcluster_network_egress_gb egress_gb Standard (per unit) $0.05
vcluster_lb_hours lb_hours Standard (per unit) $0.025
vcluster_private_node_hours private_node_hours Standard (per unit) $1.00

Example: AI Cloud pricing

For a GPU-focused AI Cloud selling compute to AI teams, you might set higher GPU prices and include a premium for dedicated infrastructure:

# AI Cloud pricing example CPU Core-Hours: $0.08 # higher due to bare-metal premium Memory GB-Hours: $0.015 # ECC memory cost Storage GB-Hours: $0.002 # NVMe SSD storage Instance Hours: $0.25 # base platform fee GPU Hours (default): $3.50 # A100 baseline GPU Hours (H100): $5.50 # premium for latest gen Network Egress/GB: $0.08 # cross-DC traffic LoadBalancer Hours: $0.05 # dedicated L4 LB Private Node Hours: $2.00 # full node reservation

Example: Internal platform team pricing

For an IDP (Internal Developer Platform) team doing internal chargebacks, use lower prices that reflect shared infrastructure costs:

# Internal platform team (chargeback) pricing CPU Core-Hours: $0.02 # internal cost only Memory GB-Hours: $0.005 # shared cluster memory Storage GB-Hours: $0.0005 # network-attached storage Instance Hours: $0.05 # minimal overhead fee GPU Hours (default): $1.00 # amortized GPU cost Network Egress/GB: $0.01 # internal network LoadBalancer Hours: $0.01 # shared ingress Private Node Hours: $0.50 # bare-metal amortization
!
If you see usage events in Lago but invoices show $0.00, it means the charge amounts are still set to their defaults ($0). Edit the plan charges in the Lago UI.

To price H100 hours differently from L40S hours in Lago, add charge filters on the sku (or gpu_type) event property. vBilling sends every dimension as an event property: sku, region, capacity_type, billing_mode, tenant_class and tenant_cluster. Spot and preemptible discounts are a filter on capacity_type.

Stripe and Metronome Pricing

Stripe: vBilling creates a Billing Meter per metric and, because Stripe meters cannot price on dimensions, a meter per SKU as new SKUs appear (for example vcluster_gpu_hours__nvidia_h100_80gb_hbm3). Create a metered price on each meter you charge for. To subscribe new tenants automatically, tag the prices metadata[vbilling_plan]=vcluster-standard (or the tenant's plan annotation) and enable stripe.autoSubscribe.

Metronome: vBilling creates SUM billable metrics named <Metric> [<code>], each with one compound group key: region, sku, capacity_type, billing_mode, tenant_cluster for GPU hours. On your rate card, add a usage product per metric with pricing_group_key set to the dimensions you price on (for example [region, sku, capacity_type]) and presentation_group_key: [tenant_cluster] for invoice lines per tenant cluster. Rate cards carry the currency, so use one rate card per currency (for example AUD and NZD). Point metronome.rateCard or a tenant's plan annotation at it.