Skip to main content

Prerequisites

From CostGraph, one thing: a deployment API key, created in the dashboard under Settings > API keys. It is the only credential you need. The image registry authenticates with the same key, and the chart wires that up for you. Self-hosting still starts with an account on app.costgraph.ai. Sign up there, and that account is where you create the deployment API key, pick your plan, and manage your subscription and billing. The account is for provisioning only: your cost data never leaves the deployment you run. From your side:
  • Kubernetes and Helm 3+
  • Postgres 14+, with citext and pg_trgm
  • Redis
  • VictoriaMetrics
  • The URL your users will reach the dashboard on
pg_partman and pg_cron are not strictly required, but install them. Without pg_partman every row lands in one default partition and large tables degrade; without pg_cron partition maintenance is never scheduled, and inserts eventually fail.Managed Postgres (RDS, Cloud SQL) often ships neither. The install still succeeds and warns you.
Sign-in needs no configuration, but a new deployment starts with no organisation and no users. The first person to use it opens the dashboard and chooses Create an account, which creates the organisation and makes them its owner; everyone else is invited from Settings. An email that already has a CostGraph account elsewhere is fine, because the account here belongs to this deployment. Their cost data stays in your network.

Install

1

Add the Helm repository

2

Write a values file

global.appBaseURL must be the address people actually open in a browser, not an internal Service DNS name. It shapes the links in invitation email.
3

Install

A preflight check runs first and fails the install with the reason if the database cannot support the schema.

Evaluating without a database

To try it before standing up Postgres, Redis and VictoriaMetrics, replace those three blocks with bundled.enabled: true on each and the chart runs them in-cluster:
Evaluation only. Single replica, no backups, and the bundled Postgres image carries neither pg_partman nor pg_cron, so partition maintenance never runs. A database holding your cost history must outlive a Helm release.

Keeping credentials out of values

For production, put credentials in Secrets you manage and reference them with existingSecret rather than inline values, so nothing sensitive is written into Helm release history:
When you use global.controlPlane.existingSecret, create the image pull secret yourself as well and set global.imagePullSecrets:
Then name it on each component too. global.imagePullSecrets does not reach the components rendered by the shared application chart, so it has to be repeated; the render fails with a message naming the component if you miss one.
See using your own registry credentials.

Network access it needs

Outbound HTTPS (443) to these hosts: Nothing needs to reach the deployment from outside your network, unless you connect Slack in webhook mode.

Connecting your spend

Installing the chart gives you CostGraph. It does not give it anything to report on yet. That comes from the same sources as hosted CostGraph: Connect whichever apply to you. Provider integrations need only a read-only credential and nothing deployed.

Pointing them at your install

Everything you deploy defaults to hosted CostGraph, so on a self-hosted install you redirect it at your own address.
Miss this and that cluster or machine reports to hosted CostGraph instead of to you.
For the operator, in every cluster you want costs for, including the one running CostGraph:
The agent and the API take the same treatment: give them your own address in place of api.costgraph.ai, and your deployment API key.

Connect with OAuth

Hosted CostGraph signs in to DigitalOcean, Akamai Cloud (Linode), Convex, and PlanetScale through its own registered apps. On a self-hosted install, you register your own app with each vendor you want a Connect with button for.
  1. In the vendor’s developer settings, create an OAuth app.
  2. Set its redirect URL to {APP_BASE_URL}/integrations/oauth/callback, using the value of global.appBaseURL.
  3. Set the app’s client ID and secret as OAUTH_<SLUG>_CLIENT_ID and OAUTH_<SLUG>_CLIENT_SECRET, where <SLUG> is DIGITALOCEAN, LINODE, CONVEX, or PLANETSCALE.
  4. Set OAUTH_STATE_SECRET to a base64 string that decodes to at least 32 bytes.
For PlanetScale, set the app’s organization scopes to read_organizations, read_invoices, read_databases, read_branches, and read_metrics_endpoints. PlanetScale applies scopes from the app, so after changing them, reconnect existing PlanetScale connections. Store the credentials in a Secret you manage, then reference them from backend.deployment.env. The --namespace must match the namespace of your Helm release:
Add the same pair of entries for each provider you register. Providers without a client ID and secret show the token form instead.

Pricing hardware that is not in a cloud

A resource in a public cloud is priced from that provider’s published rates, matched on its instance type and region. Hardware you own has no published rate, so you register what it costs you and tag the resources that bill at it. This applies whether you measure them with the operator or the agent. Register the rate once, then tag everything that bills at it:
A pricing ID is a rate you register once: what a piece of hardware you own actually costs you. You then tag it onto the resources that bill at it. Without one, hardware you own is measured but has nothing to price against.

Pricing IDs

Registering rates, the label spellings each platform accepts, and how a rate is inherited from a node to the volumes on it.
Labels are inherited: tagging a node prices the volumes attached to it, so you rarely need to label every object. A node is in no namespace, so labelling a namespace never changes a node’s own rate.

Operating

Resources are named costgraph-selfhosted, independent of the release name. helm status costgraph prints them. Check health at any time. This reads the database and makes no outbound call:
If support asks for a diagnostic bundle. It contains no secrets and no cost data:

If the deployment can’t bootstrap

On startup the backend registers with CostGraph and receives a signed configuration document. That document requires an active license on your organization, so bootstrap fails while your plan is still being set up:
Sign in at app.costgraph.ai and check that the organization holding your deployment API key has an active plan. If it does and the error persists, send the diagnostic bundle to [email protected]. Sign-in on your deployment depends on the same document, because the Cognito user pool it names arrives inside it. A deployment that hasn’t bootstrapped reports Auth UserPool not configured on the sign-up form. That message means the configuration hasn’t arrived yet, not that you’re missing a setting: there is no Cognito value for you to fill in. Fix the bootstrap and sign-in works.

The one thing you must not delete

On first install the chart generates encryption keys into a Secret named costgraph-selfhosted-generated. Everything you have saved is tied to them. Helm keeps this Secret across upgrades and uninstalls, so normally you need do nothing. But it cannot be regenerated.
This preservation is done with Helm’s lookup, which only reads the cluster under helm install and helm upgrade. Argo CD renders with helm template, which has no cluster access, so under Argo these keys are regenerated on every sync. See Install path.
If that Secret is deleted, every API key your team issued stops working and every connected cloud account has to be reconnected, because the stored credentials can no longer be decrypted. Back it up alongside the database, and keep the two together. A database restored next to a different Secret is a database whose credentials cannot be read.

Upgrades

Upgrade in place. Pending migrations are applied on start:
Back the database up first. Migrations are not reversible, so rolling the release back does not roll the schema back. A failed upgrade recovers forwards, not by helm rollback.
The chart’s README carries the full recovery procedure for a failed upgrade:

Every setting

values.yaml documents each setting inline, next to the value it controls:

Values reference

The component pattern, the three value tiers, and how to enable or size a single component.