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.

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. Pinning one component never moves another.

Enabling and disabling

<component>.enabled gates the component. 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. 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. 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

global.imagePullSecrets does not reach every 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:

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 component, 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 every component; see the render-time check above.

Integration sign-in

Connect with for DigitalOcean, Akamai Cloud (Linode), Convex, and PlanetScale is configured as environment entries under backend.deployment.env, not as chart values. Register your own OAuth app with each vendor and set its redirect URL to {APP_BASE_URL}/integrations/oauth/callback. See Connect with OAuth for an example. A provider with no client ID and secret shows the token form instead.

Slack

Connects Graph AI to a Slack app you create. Slack is on when clientId or existingSecret is set, and needs costgraphAi.enabled. See Slack for the setup.
string
default:"socket"
socket opens an outbound connection to Slack. webhook has Slack call your CostGraph URL and adds three Slack paths to the chart’s ingress.
string
Name of a Secret you manage, with keys SLACK_CLIENT_ID, SLACK_CLIENT_SECRET, SLACK_SIGNING_SECRET, and SLACK_APP_TOKEN (socket mode only). Takes precedence over the four values that follow, so no credential is stored in your values file.
string
Client ID from your Slack app’s Basic Information > App Credentials.
string
Client secret from the same page. Required with clientId.
string
Signing secret from the same page. Required in both modes.
string
App-level token with the connections:write scope, starting with xapp-. Required in socket mode.

Ingress

The Ingress fans one host out to 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.

Client addresses behind a proxy

The backend reads the client’s address from the X-Forwarded-For header only when the request comes from a trusted proxy. Sign-in rate limits and request logs use that address. By default, every private network range is trusted. If your ingress controller or load balancer forwards from a narrower range, set it with the TRUSTED_PROXIES environment entry, as comma-separated CIDRs:
The backend doesn’t start if a CIDR is invalid.

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, and ENABLE_NETWORK_TRACE. Turning one off drops it from the dashboard. ENABLE_NETWORK_TRACE is on by default. Set it to "false" to stop turning network flow tracing into traffic, so Context shows no traffic lines.
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. A Service is rendered for each of the two Deployments, 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 and operatorPrometheus.scrapeTargets stay at component level. 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.