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 inChart.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 invalues.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: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.
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
The supported way to use your own credentials, which is required when you setglobal.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:What costgraph-common adds
Each of these exists becauseapplication cannot express it, not because a
second renderer was wanted.
Install path: use helm upgrade, not Argo CD
This is verifiable: two consecutivehelm 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(keysapi-key-secretandintegrations-encryption-key) and, if you use the bundled database,costgraph-selfhosted-postgres(keypassword) before the first sync, and let the chart adopt them. Better still, do not use the bundled database: pointglobal.postgres.existingSecretat a Secret you manage, and the generated password stops being part of the problem. -
Tell Argo to ignore the drift.
ignoreDifferenceson its own only changes what Argo compares, not what it applies, so a sync still overwrites the Secret. Pair it withRespectIgnoreDifferences=true:This protects an existing Secret, never one Argo is creating for the first time.costgraph-selfhosted-generatedmust already exist before the first sync, so this option is a complement to pre-creating it, not an alternative.
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 anexistingSecret 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 bycostgraph-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. Incostgraph-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....
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.