settings.yaml
Spendemon reads its runtime configuration from a settings.yaml file.
This file controls:
- which clusters are queried
- the cost rates used for estimates
- which namespaces should be treated as shared overhead
- whether OIDC authentication and authorization is enabled
Local username/password auth is configured entirely through environment variables, not settings.yaml.
By default the app reads settings.yaml from the project root. You can override the path with SETTINGS_FILE_PATH.
Example
Synced from the repo
Starter settings-example.yaml
This starter file is rendered from the repository root so the docs stay aligned with the shipped example config.
clusters:
- name: cluster-1
prometheusUrl: http://localhost:9090
# Optional: customize the sidebar with your company name and logo
# branding:
# companyName: Acme Corp
# logoUrl: https://example.com/logo.svg
costs:
cpuCore: 10
memoryGb: 20
storageGb: 5
oidc:
enabled: false
debug: false
issuer: ${OIDC_ISSUER}
clientId: ${OIDC_CLIENT_ID}
clientSecret: ${OIDC_CLIENT_SECRET}
adminGroup: scientists
viewerGroup: mathematicians
# if for example using dex idp, use groups as extra scopes to get the user groups in the id token, so that spendemon can use them for authorization
extraScopes: ''
sharednamespaces:
- kube-system
Top-level keys
The runtime parser understands these top-level sections:
clusterscostssharednamespacesbrandingoidc
One detail is easy to miss:
sharednamespacesis all lowercase and written as one word
Deployment replica behavior is not part of the runtime file. If you use Helm, keep that in chart values under ha.enabled.
clusters
Use clusters to define the Prometheus-backed environments Spendemon should query.
name: cluster label shown throughout the UIprometheusUrl: base URL for that cluster's Prometheus server
Rules and notes:
- at least one cluster is required
- every cluster must include both
nameandprometheusUrl prometheusUrlmust usehttporhttps— other schemes (e.g.file://,ftp://) are rejected at startup- the URL must be reachable from where Spendemon is running
Example:
clusters:
- name: production
prometheusUrl: https://prometheus-prod.example.com:9090
- name: staging
prometheusUrl: https://prometheus-staging.example.com:9090
costs
The costs block defines the pricing inputs used to turn resource data into estimated cost.
cpuCore: cost applied to CPU coresmemoryGb: cost applied to memory in GBstorageGb: cost applied to ephemeral storage in GB
All three values must be non-negative numbers.
Spendemon prefers Kubernetes resource requests when calculating pod cost:
- CPU from
kube_pod_container_resource_requests{resource="cpu",unit="core"} - memory from
kube_pod_container_resource_requests{resource="memory",unit="byte"} - ephemeral storage from available storage request metrics
If CPU or memory requests are missing, Spendemon falls back to observed usage for estimation and marks those pods as estimated in the report.
sharednamespaces
Use sharednamespaces for platform namespaces whose cost should be redistributed across the other namespaces in the same cluster.
Typical examples:
kube-systemmonitoringingress-nginx
Behavior:
- matching namespaces are excluded as standalone chargeback targets
- their totals are split evenly across the remaining namespaces in that cluster
- the redistributed values are also spread across the pods inside each recipient namespace
Example:
sharednamespaces:
- kube-system
- monitoring
- ingress-nginx
branding
The optional branding block adds a custom company name and logo to the sidebar.
companyName: display name shown in the sidebar — required for the block to take effectlogoUrl: URL of an image to display above the company name — optional
If branding is omitted or companyName is empty, the sidebar falls back to the default Spendemon branding.
Example:
branding:
companyName: Acme Corp
logoUrl: https://example.com/logo.svg
oidc
The oidc block enables authentication and role-based authorization through NextAuth and an OpenID Connect provider.
Supported keys:
enabled: turns OIDC on or offdebug: logs resolved memberships and raw token claims to the server logs during sign-in — keep thisfalsein production as it prints sensitive JWT payload data (groups, roles, email) to stdout and any connected log aggregatorissuer: OIDC issuer URLclientId: client IDclientSecret: client secretadminGroup: membership that grants admin accessviewerGroup: membership that grants viewer accessextraScopes: extra scopes appended to the defaultopenid email profile
extraScopes is stored in runtime YAML as a single string and can be space-separated or comma-separated:
oidc:
extraScopes: groups offline_access
or:
oidc:
extraScopes: groups,offline_access
The parser will normalize both forms into a deduplicated list.
Environment placeholders
Runtime OIDC string fields can reference environment variables with ${...} placeholders.
Example:
oidc:
enabled: true
issuer: ${OIDC_ISSUER}
clientId: ${OIDC_CLIENT_ID}
clientSecret: ${OIDC_CLIENT_SECRET}
adminGroup: ${OIDC_ADMIN_GROUP}
viewerGroup: ${OIDC_VIEWER_GROUP}
When OIDC is enabled, these values must resolve successfully at runtime.
Authorization model
Spendemon currently uses two roles:
viewer: can access the main app and read-only API endpointsadmin: can also access/settingsand update configuration through/api/settings
Admins also inherit viewer access.
credentials auth
Spendemon also supports local username/password sign-in, but that mode lives outside settings.yaml.
Use these environment variables instead:
AUTH_MODE=credentialsNEXTAUTH_SECRET- one or more local accounts:
LOCAL_ADMIN_USERNAMEwith eitherLOCAL_ADMIN_PASSWORDorLOCAL_ADMIN_PASSWORD_HASHLOCAL_VIEWER_USERNAMEwith eitherLOCAL_VIEWER_PASSWORDorLOCAL_VIEWER_PASSWORD_HASH
Rules and notes:
- at least one local account is required in credentials mode
- admins automatically inherit viewer access
- if both
*_PASSWORDand*_PASSWORD_HASHare set for the same account, Spendemon fails fast on startup - password hashes must use the format
scrypt:<saltHex>:<hashHex>
Example:
AUTH_MODE=credentials
NEXTAUTH_SECRET=replace-with-a-long-random-string
LOCAL_ADMIN_USERNAME=admin
LOCAL_ADMIN_PASSWORD_HASH=scrypt:7b91d3c5f6f6c8e0a6f2e1b4c5d6e7f8:6b1a5f8f0a7c4b6e6b667f6c2852e7416bfe2f521f3473df84c62fb4ef13a4dd9f32831f4a1dd4ec9e4b1f69aa389a6d2f31f2f5fd70888e387de0d7e47355d6
Generate a hash with Node.js:
node -e "const { randomBytes, scryptSync } = require('crypto'); const salt = randomBytes(16); const hash = scryptSync(process.argv[1], salt, 64); console.log(`scrypt:${salt.toString('hex')}:${hash.toString('hex')}`)" 'change-me'
Defaults and validation
If some sections are omitted, Spendemon falls back to these defaults:
costs: all zerossharednamespaces: empty listbranding: disabled (default Spendemon branding)oidc.enabled:falseoidc.debug:falseoidc.adminGroup:adminoidc.viewerGroup:vieweroidc.extraScopes: empty list
Validation rules to keep in mind:
clustersmust exist and contain at least one entryclusters[].prometheusUrlmust be a validhttporhttpsURLcostsmust be non-negative numbersoidc.enabledandoidc.debugmust betrueorfalse- unknown keys inside supported sections are rejected
Example with everything enabled
clusters:
- name: production
prometheusUrl: https://prometheus-prod.example.com:9090
costs:
cpuCore: 12.5
memoryGb: 1.8
storageGb: 0.12
sharednamespaces:
- kube-system
- monitoring
branding:
companyName: Acme Corp
logoUrl: https://example.com/logo.svg
oidc:
enabled: true
debug: false
issuer: ${OIDC_ISSUER}
clientId: ${OIDC_CLIENT_ID}
clientSecret: ${OIDC_CLIENT_SECRET}
adminGroup: platform-admins
viewerGroup: engineering
extraScopes: groups