Skip to main content
The CostGraph charts (costgraph-selfhosted and costgraph-operator) share one configuration contract. Every component in them is declared the same way, is enabled the same way, and is sized the same way, whichever Kubernetes object it ends up rendering. This page describes that contract so you can configure any component without learning a new shape for each one.

How a component is rendered

Two things render objects in these charts. stakater/application 9.3.1 is the base renderer for Deployments and Services. Components are declared as aliases of it in Chart.yaml and configured entirely from values. In costgraph-selfhosted those aliases are backend, dashboard, ingestionApi, aggregator and redis; in costgraph-operator it is operatorKubernetes.
Chart.yaml
costgraph-common is a library chart layered on top. It renders what application cannot, and nothing it does not have to: the StatefulSets (postgres, victoriametrics), the vmalert Deployments, the flowtrace DaemonSet in the operator chart, the Ingress, the ConfigMaps, the Secrets and the hook Jobs.
You never call costgraph-common yourself. It is a library chart consumed by the two application charts; it exists in this document only to explain why some components are rendered outside application while keeping the same values shape.

The component pattern

A component is a top-level key in values.yaml. Under it, the workload lives in a block named after the Kubernetes kind it produces - deployment:, statefulSet: or daemonSet: - and the inner keys of that block are the same in every case:
replicas, image, resources, env, envFrom, ports, volumes, volumeMounts, securityContext, containerSecurityContext, imagePullSecrets, automountServiceAccountToken, priorityClassName, command, args, strategy, nodeSelector, tolerations, affinity, topologySpreadConstraints, podAnnotations and podLabels mean the same thing under deployment:, under statefulSet: and under daemonSet:. The kind changes; the vocabulary does not. The differences are only the ones Kubernetes itself imposes: So the bundled Postgres is sized exactly the way the API is:
env, volumes and volumeMounts are maps, not lists: env.LOG_LEVEL.value: info, or env.DB_URL.valueFrom.secretKeyRef. A map means --set can override one entry without clobbering its siblings. Probes take an enabled key alongside the usual probe fields.

Images

Every image is a literal on its own component’s workload block:
There is no shared image value and no per-component alias root. Pinning one component never moves another.

Enabling and disabling

<component>.enabled gates the component. For an application alias the gate is wired as the condition on the dependency in Chart.yaml, so a disabled component renders nothing at all rather than rendering and being filtered.
<component>.deployment.enabled (or .statefulSet.enabled / .daemonSet.enabled) is a second, narrower switch: it keeps the component’s Service and configuration but drops the pods. The three bundled datastores are the exception. postgres, redis and victoriametrics are switched on by global.postgres.bundled.enabled, global.redis.bundled.enabled and global.metricsStore.bundled.enabled rather than by an enabled key of their own, because the helpers that build the connection URLs are called from subchart aliases and a subchart can only read global. Everything else about their shape lives in their own top-level block, like every other component.
backend.enabled exists and defaults to true, but an install without the API is not a working install. Leave it on.

The three value tiers

global.* holds facts about the deployment, not about any one component: connection strings, credentials, base URLs. A subchart can only see global, so anything more than one component reads has to live there. In costgraph-selfhosted that is global.postgres, global.metricsStore, global.redis, global.controlPlane, global.appBaseURL, global.corsAllowedOrigins, global.pricingApiKey, global.analytics and global.imagePullSecrets. Per-component blocks hold what differs: the component’s own image, replica count, resources, environment, probes and scheduling. There are no global scheduling or pod-annotation defaults - nodeSelector, tolerations, affinity and podAnnotations are set on the component you want to move. global.podSecurityContext and global.securityContext are narrower than they look: they apply to the pre-install and pre-upgrade Jobs, which are not components and have no block of their own. Components carry their own securityContext and containerSecurityContext.

Using your own registry credentials

application does not template imagePullSecrets, so global.imagePullSecrets does not reach an alias component. Each component names its pull secret in its own <component>.deployment.imagePullSecrets.Because getting this half-right is silent, the chart checks it. Setting global.imagePullSecrets while backend, dashboard, ingestionApi or aggregator still names the chart’s own costgraph-selfhosted-registry Secret fails the render, with a message naming the component to fix. That Secret is not created when global.imagePullSecrets is set, so the render would otherwise succeed and the pods would fail to pull.
The supported way to use your own credentials, which is required when you set global.controlPlane.existingSecret:

Deploying or sizing a single component

Because every component reads the same keys, one component is changed without touching the rest. Run only part of the stack:
Size just one component:

What costgraph-common adds

Each of these exists because application cannot express it, not because a second renderer was wanted.

Install path: use helm upgrade, not Argo CD

The chart preserves generated credentials by reading the live Secret with Helm’s lookup function. lookup returns data only under helm install and helm upgrade. It returns nothing under helm template, which has no cluster access.Argo CD renders manifests with helm template. Under Argo, the preservation branch never fires, so api-key-secret, integrations-encryption-key and the bundled Postgres password are regenerated on every sync. That invalidates every issued API key and makes already-encrypted integration credentials unreadable.
This is verifiable: two consecutive helm template runs of the same chart, with the same values, produce different values for api-key-secret. Two consecutive helm upgrade runs do not. The supported install path is helm install / helm upgrade. If you must deploy through Argo CD, pick one of:
  • Pre-create the Secrets out of band. Create costgraph-selfhosted-generated (keys api-key-secret and integrations-encryption-key) and, if you use the bundled database, costgraph-selfhosted-postgres (key password) before the first sync, and let the chart adopt them. Better still, do not use the bundled database: point global.postgres.existingSecret at a Secret you manage, and the generated password stops being part of the problem.
  • Tell Argo to ignore the drift. ignoreDifferences on its own only changes what Argo compares, not what it applies, so a sync still overwrites the Secret. Pair it with RespectIgnoreDifferences=true:
    This protects an existing Secret, never one Argo is creating for the first time. costgraph-selfhosted-generated must already exist before the first sync, so this option is a complement to pre-creating it, not an alternative.
The generated Secret carries helm.sh/resource-policy: keep, so it survives a helm uninstall. That protects it from deletion, not from a re-render that overwrites it.

Credentials and external services

Every credential the charts accept has an existingSecret form. Prefer it: a value passed inline is stored in values and in Helm release history.
string
Deployment API key issued during onboarding. Also authenticates image pulls from registry.costgraph.ai.
string
Name of a Secret you manage holding control-plane-api-key and pricing-api-key. Copy the control-plane key into pricing-api-key unless you were issued a separate pricing key; the backend does not start without it. Takes precedence over apiKey. Requires global.imagePullSecrets, since the chart cannot read a Secret to build the registry pull secret from.
string
CostGraph control plane endpoint. Defaults to https://api.costgraph.ai.
string
CostGraph’s published public key, shipped with the chart. Not a secret. Do not remove it; the render fails without it.
string
Connection string for an external Postgres.
string
Name of a Secret you manage holding postgres-url and postgres-password. Takes precedence over url and password.
boolean
Apply pending database migrations on start. Leave on unless you apply them as a separate step.
boolean
Run the Postgres described by the postgres component in-cluster instead of using an external database. Sized with postgres.storage, postgres.storageClass and postgres.statefulSet.resources. Not recommended for production, and see the Argo CD caveat above: its password is a generated value.
string
URL of an external VictoriaMetrics or Prometheus-compatible store.
boolean
Run the VictoriaMetrics described by the victoriametrics component. Retention is victoriametrics.retentionPeriod (months); storage is victoriametrics.storage and victoriametrics.storageClass.
string
Redis connection URL. Use global.redis.existingSecret (key redis-url) instead when the URL contains a password, so it is not stored in a ConfigMap in plain text.
boolean
Run the Redis described by the redis component. This is the condition on the redis alias, so leaving it off renders nothing.
string
required
The URL your users open CostGraph on. Must be the address people actually reach in a browser, not an internal Service DNS name, and must start with http:// or https://. Also the default allowed browser origin.
string
Browser origins allowed to call the API, comma-separated. Defaults to appBaseURL.
string
Key used to download the pricing catalog. Leave empty to reuse global.controlPlane.apiKey, which is what most installs do.
boolean
Anonymous product analytics. Set to false to send nothing.
array
Pull secrets for the components this chart renders itself. Does not reach the application aliases - see the render-time check above.

Ingress

The Ingress is rendered by costgraph-common so its paths can address several Services by port number.
boolean
Render the Ingress. The render fails if this is on and ingress.hosts is empty.
string
Ingress class.
object
Annotations on the Ingress object.
array
Hosts, each with its own paths. The API and remote-write paths are added automatically for whichever of backend, dashboard and ingestionApi are enabled.
array
Standard Kubernetes Ingress TLS blocks.

Preflight

boolean
A preflight Job that runs before install and upgrade and fails the release with the real reason if the database cannot support the schema. Turn it off only if the database is provisioned after the release.

Component blocks

object
The API, and the component that creates the ServiceAccount the rest of the release runs as (backend.rbac.serviceAccount). Configure it through backend.deployment.replicas, backend.deployment.resources, backend.deployment.image, backend.deployment.env and backend.service.ports.
object
The web UI. dashboard.enabled, dashboard.deployment.image, and dashboard.apiBaseURL when the API is served from a different host than global.appBaseURL. dashboard.cognitoOAuthDomain and dashboard.graphAiBaseURL are empty unless CostGraph has set those up for your deployment.
object
Receives metrics from the CostGraph operator running in your clusters. ingestionApi.enabled, ingestionApi.logLevel, and ingestionApi.maxBodyBytes - raise the last if clusters with many nodes report remote-write bodies rejected as too large.
object
Turns raw samples into per-workload costs. Without it, metrics arrive but cost breakdowns stay empty. aggregator.features toggles the cost dimensions computed (ENABLE_GPU, ENABLE_HPA, ENABLE_NETWORK_POLICY, ENABLE_EPHEMERAL_STORAGE, ENABLE_DISK_IOPS); turning one off drops it from the dashboard.
object
Evaluates the recording rules the cost maths reads. It renders two Deployments from vmalert.deployment, one per rule set: long-window rules feed the daily rollups, short-window rules the recent views. The rules themselves come from the aggregator image, so aggregator.deployment.image.tag is required whenever vmalert is enabled.It has no service: or rbac: block. costgraph-common renders a Service for each of the two Deployments itself, so /api/v1/rules is reachable even when a rule produces no samples, and vmalert runs with no ServiceAccount of its own.
object
The bundled Redis, gated by global.redis.bundled.enabled. It has no enabled key of its own. Configure an external Redis under global.redis instead.
object
The bundled Postgres StatefulSet, gated by global.postgres.bundled.enabled. postgres.database, postgres.username, postgres.password (generated per install when empty), postgres.storage, postgres.storageClass, and the workload under postgres.statefulSet.
object
The bundled metrics store, gated by global.metricsStore.bundled.enabled. victoriametrics.retentionPeriod (months), victoriametrics.storage, victoriametrics.storageClass, and the workload under victoriametrics.statefulSet.
string
Label for this deployment in the CostGraph dashboard, so several of them can be told apart. Defaults to the Helm release name.
string
Overrides the chart name used in the generated labels.

Migrating an existing values file

Old paths still parse. Helm does not warn about a key nothing reads, so a values file written against an earlier chart installs cleanly and quietly falls back to the chart defaults. Check every override you rely on against these tables.

costgraph-selfhosted

global.postgres.bundled.enabled, global.redis.bundled.enabled and global.metricsStore.bundled.enabled are unchanged: they are still the switches for the bundled datastores.

costgraph-operator

All four CostGraph components - operatorKubernetes, operatorPrometheus, aiGatewayScraper and flowtrace - now take the same shape: enabled, applicationName, a workload block, and a component-specific config. flowtrace.maxUnavailable stays at component level, because it is a rollout setting rather than a pod setting, and operatorPrometheus.scrapeTargets stays at component level too. enabled stays at component level on all four; <component>.deployment.enabled / .daemonSet.enabled is the narrower switch that drops the pods.

Object names lose the release prefix

These are live objects being renamed, so plan the upgrade. In costgraph-operator: fullnameOverride no longer reaches these. It now affects only the ClusterRole (<fullname>-view), the ClusterRoleBinding (<fullname>) and the ServiceAccount. Each component’s objects are named from its applicationName. In costgraph-selfhosted, objects were <release>-costgraph-selfhosted... and are now costgraph-selfhosted....
A renamed Deployment or DaemonSet is a new object; a renamed StatefulSet will not adopt the old one’s PersistentVolumeClaims and provisions empty volumes instead. If you run the bundled Postgres or the bundled metrics store, back up first. This is another reason to point the release at datastores you operate.

Install

Prerequisites, a starting values file, and what to point at your own address.

Operator configuration

The same contract, applied to the chart that runs in each measured cluster.