Skip to content

Budget Configuration Guide

Budgets allow you to set spending limits for your infrastructure and receive alerts when costs exceed defined thresholds. By configuring budgets in FinFocus, you can proactively manage cloud spending and prevent unexpected overruns before they happen.

This guide covers how to define monthly budgets, set up alerts for actual and projected costs, and integrate budget checks into your CI/CD pipelines.

Target Audience: End Users, DevOps Engineers, FinOps Practitioners

Prerequisites:

Learning Objectives:

  • Configure monthly budget amounts and currencies
  • Set up alerts for actual vs. forecasted costs
  • Integrate budget enforcement into CI/CD workflows
  • Troubleshoot common budget configuration issues

Estimated Time: 10 minutes



Get started with budgets in under 5 minutes.

Create or edit your ~/.finfocus/config.yaml to define a budget:

# yaml-language-server: $schema=https://rshade.github.io/finfocus/schemas/config.json
cost:
budgets:
amount: 500.00
currency: USD
period: monthly
alerts:
- threshold: 80
type: actual
- threshold: 100
type: forecasted

Run a cost projection to see how your infrastructure compares to the budget:

Terminal window
finfocus cost projected --pulumi-json plan.json

Expected Output:

Budget: $500.00 (75% used)
[=====================>......] $375.00 / $500.00
RESOURCE ADAPTER MONTHLY CURRENCY NOTES
aws:ec2/instance:Instance aws-spec $375.00 USD t3.xlarge

Budget status display with color-coded threshold bars and emoji indicators

Figure 1: Budget display showing usage against defined threshold.

What’s Next?


Complete reference for all budget configuration options.

Configuration is stored in ~/.finfocus/config.yaml.

For IDE autocomplete and validation, add this comment to your config file:

# yaml-language-server: $schema=https://rshade.github.io/finfocus/schemas/config.json
Option Type Default Required Description
amount number - Yes Budget amount in specified currency
currency string "USD" No ISO 4217 currency code (USD, EUR, GBP)
period string "monthly" No Budget period (daily, weekly, monthly, yearly)
alerts list [] No Alert thresholds (see Alerts Options below)
Option Type Default Required Description
threshold number - Yes Percentage of budget (1-100)
type string "actual" No Alert type (actual, forecasted)
notifications list [] No Destinations to notify (see Notifications)

Override configuration with environment variables:

Variable Description Example
FINFOCUS_BUDGET_AMOUNT Override budget amount 500.00
FINFOCUS_BUDGET_CURRENCY Override currency EUR
FINFOCUS_BUDGET_EXIT_ON_THRESHOLD Exit process when budget threshold exceeded true
FINFOCUS_BUDGET_EXIT_CODE Exit code to use when enforcement triggers 2
FINFOCUS_NOTIFY Send budget notifications when --notify is not set true

See Configuration Reference for complete details.


Practical examples for common budget scenarios.

Use Case: Simple enforcement to ensure costs don’t exceed a hard limit.

Configuration:

cost:
budgets:
amount: 1000.00
currency: USD
period: monthly
alerts:
- threshold: 100
type: actual

Usage:

Terminal window
finfocus cost projected --pulumi-json plan.json

Explanation:

This configuration sets a hard limit of $1000/month. If actual costs exceed this amount, the CLI will display a warning and exit with a non-zero status code (if configured).

See complete example.


Use Case: Progressive warnings to catch cost creep early.

Configuration:

cost:
budgets:
amount: 2000.00
currency: USD
alerts:
- threshold: 50
type: actual
# Early warning at 50%
- threshold: 80
type: forecasted
# Warning if projected to reach 80%
- threshold: 100
type: actual
# Critical alert at 100%

Explanation:

This setup provides early visibility. You’ll get notified when you hit 50% of budget, if you’re projected to hit 80%, and finally when you breach 100%.

See complete example.


Use Case: Fail build pipelines when infrastructure changes exceed budget.

Configuration:

cost:
budgets:
amount: 500.00
alerts:
- threshold: 100
type: forecasted

Usage:

Terminal window
# In your CI pipeline script
finfocus cost projected --pulumi-json plan.json || {
echo "Budget exceeded!"
exit 1
}

Explanation:

By using type: forecasted at 100% threshold, FinFocus checks if the new infrastructure plan will push total costs over budget. If yes, it returns a non-zero exit code, stopping the deployment.

See complete example.


Note: FinFocus supports two budget configuration styles. The Quick Start examples above use the flat format (cost.budgets.amount) for simple single-budget setups. The scoped format below uses cost.budgets.global with nested providers, tags, and types sections for multi-level budget control. Both are valid; use flat for simplicity or scoped when you need per-provider/tag/type breakdowns.

Define budgets at multiple levels for granular cost control: global, per-provider, per-tag, and per-resource-type.

Track and limit spending per cloud provider (AWS, GCP, Azure).

Configuration:

cost:
budgets:
# Global budget applies to all resources (required when scopes defined)
global:
amount: 5000.00
currency: USD
period: monthly
alerts:
- threshold: 80
type: actual
# Per-provider budgets
providers:
aws:
amount: 3000.00
gcp:
amount: 2000.00
azure:
amount: 1000.00

Usage:

Terminal window
# View all budgets including provider breakdown
finfocus cost projected --pulumi-json plan.json
# Filter to show only provider budgets
finfocus cost projected --pulumi-json plan.json --budget-scope=provider
# Filter to a specific provider
finfocus cost projected --pulumi-json plan.json --budget-scope=provider=aws

Example Output:

BUDGET STATUS
═════════════════════════════════════════════════════════════
GLOBAL
Budget: $5,000.00 | Spend: $3,250.00 (65.0%)
████████████████████░░░░░░░░░░ OK
BY PROVIDER
───────────────────────────────────────────────────────────────
aws Budget: $3,000.00 | Spend: $2,100.00 (70.0%) OK
gcp Budget: $2,000.00 | Spend: $1,150.00 (57.5%) OK
azure Budget: $1,000.00 | Spend: $0.00 (0.0%) OK
Overall Health: OK

Key Points:

  • Provider names are case-insensitive (aws, AWS, Aws all match)
  • Provider is extracted from resource type (e.g., aws:ec2/instance → aws)
  • All provider budgets must use the same currency as the global budget
  • Each resource’s cost counts toward both its provider budget AND the global budget

Track costs by resource tags (e.g., team:platform, env:prod) with priority-based allocation.

Configuration:

cost:
budgets:
# Global budget applies to all resources (required when scopes defined)
global:
amount: 10000.00
currency: USD
period: monthly
# Per-tag budgets with priority ordering
tags:
- selector: 'team:platform'
priority: 100
amount: 3000.00
- selector: 'team:backend'
priority: 100
amount: 2500.00
- selector: 'env:prod'
priority: 50
amount: 5000.00
- selector: 'cost-center:*'
priority: 10
amount: 1000.00

Usage:

Terminal window
# View all budgets including tag breakdown
finfocus cost projected --pulumi-json plan.json
# Filter to show only tag budgets
finfocus cost projected --pulumi-json plan.json --budget-scope=tag

Example Output:

BUDGET STATUS
═════════════════════════════════════════════════════════════
GLOBAL
Budget: $10,000.00 | Spend: $6,500.00 (65.0%)
████████████████████░░░░░░░░░░ OK
BY TAG
───────────────────────────────────────────────────────────────
team:platform Budget: $3,000.00 | Spend: $2,100.00 (70.0%) OK
team:backend Budget: $2,500.00 | Spend: $1,500.00 (60.0%) OK
env:prod Budget: $5,000.00 | Spend: $4,200.00 (84.0%) WARNING
Overall Health: WARNING

Tag Selector Patterns:

Pattern Description Example Match
key:value Exact match on tag key and value team:platform matches resources with team=platform
key:* Wildcard match on any value for the key env:* matches env=prod, env=dev, env=staging

Priority-Based Allocation:

When a resource matches multiple tag budgets, cost is allocated to the highest priority budget only:

  • Higher priority values take precedence (100 > 50 > 10)
  • If multiple budgets share the same priority, the first alphabetically wins
  • A warning is emitted when priority ties occur

Configuration Tips:

  • Use specific selectors (team:platform) for known teams
  • Use wildcards (cost-center:*) as catch-all budgets with lower priority
  • Ensure higher-priority budgets are more specific to avoid allocation conflicts

Track and limit spending per resource type (e.g., aws:ec2/instance, gcp:compute/instance).

Configuration:

cost:
budgets:
# Global budget applies to all resources (required when scopes defined)
global:
amount: 10000.00
currency: USD
period: monthly
# Per-resource-type budgets
types:
'aws:ec2/instance':
amount: 2000.00
'aws:rds/instance':
amount: 3000.00
'gcp:compute/instance':
amount: 1500.00

Usage:

Terminal window
# View all budgets including type breakdown
finfocus cost projected --pulumi-json plan.json
# Filter to show only resource type budgets
finfocus cost projected --pulumi-json plan.json --budget-scope=type

Example Output:

BUDGET STATUS
═════════════════════════════════════════════════════════════
GLOBAL
Budget: $10,000.00 | Spend: $5,500.00 (55.0%)
█████████████████░░░░░░░░░░░░░░ OK
BY TYPE
───────────────────────────────────────────────────────────────
aws:ec2/instance Budget: $2,000.00 | Spend: $1,200.00 (60.0%) OK
aws:rds/instance Budget: $3,000.00 | Spend: $2,700.00 (90.0%) CRITICAL
Overall Health: CRITICAL

Key Points:

  • Resource types use exact matching (case-sensitive)
  • Type is extracted from Pulumi resource type (e.g., aws:ec2/instance:Instance → aws:ec2/instance)
  • All type budgets must use the same currency as the global budget
  • Each resource’s cost counts toward its type budget AND the global budget
  • Unconfigured resource types do not appear in the BY TYPE section

An alert threshold can send a message to Slack or to any HTTPS endpoint when a cost command finds that the threshold was crossed. Notifications are opt-in per run, so local runs stay quiet while CI sends.

Add notifications to any alert, in any scope (global, providers.<name>, tags[], types.<pattern>). Put destinations that use secrets in the global ~/.finfocus/config.hujson:

{
"cost": {
"budgets": {
"global": {
"amount": 100,
"currency": "USD",
"alerts": [
{
"threshold": 80,
"type": "actual",
"notifications": [
{"type": "slack", "url": "${FINFOCUS_NOTIFY_SLACK_URL}", "channel": "#finops-alerts"},
{
"type": "webhook",
"url": "https://api.example.com/budget-alert",
"method": "POST",
"headers": {"Authorization": "Bearer ${FINFOCUS_NOTIFY_API_TOKEN}"}
}
]
}
]
}
}
}
}
Field Applies to Required Description
type all Yes slack (incoming webhook) or webhook (generic HTTPS endpoint)
url all Yes https:// URL with a host, or a ${FINFOCUS_NOTIFY_*} reference
channel slack No Channel override sent with the message
method webhook No POST (default) or PUT
headers webhook No Extra request headers; values may use ${FINFOCUS_NOTIFY_*}

Check the file with finfocus config validate. Validation rejects unknown types, http:// URLs, channel on a webhook, method or headers on a Slack destination, and any variable that does not start with FINFOCUS_NOTIFY_.

Slack apps bind each incoming webhook to one channel, so Slack may ignore channel. Create one webhook per channel when you need several.

A run sends only when you pass --notify (on cost projected, cost actual, and overview) or set FINFOCUS_NOTIFY=true. An explicit --notify=false wins over the variable. An invalid FINFOCUS_NOTIFY value prints a warning and counts as false.

When a run does not opt in and a crossed threshold has destinations, nothing is sent and stderr shows one hint:

budget notifications for 1 exceeded threshold(s) were not sent; pass --notify or set FINFOCUS_NOTIFY=true

Every opted-in run that crosses a threshold sends again. FinFocus keeps no notification history between runs, and sends one message per crossed threshold and destination.

env:
FINFOCUS_NOTIFY: "true"
FINFOCUS_NOTIFY_SLACK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
steps:
- run: finfocus cost projected --pulumi-json plan.json

Write the destinations into the global config from the workflow, for example with a step that creates $FINFOCUS_HOME/config.hujson. A pull request cannot change that step for runs that receive secrets.

Terminal window
finfocus cost projected --pulumi-json plan.json --notify --dry-run
dry-run: would notify slack for global budget, 80% actual threshold

Nothing is sent. Each line names the destination type and threshold, never the URL.

Slack receives a message with the budget, current (or forecasted) spend and percentage, the threshold, and the status. A webhook destination receives a JSON event:

{
"event": "budget.threshold.exceeded",
"timestamp": "2026-10-04T15:04:05Z",
"budget": {"name": "provider:aws", "scope": "provider", "scope_key": "aws",
"amount": 50, "currency": "USD", "period": "monthly"},
"threshold": {"percentage": 100, "type": "forecasted", "value": 50},
"current": {"spend": 60, "percentage": 120},
"metadata": {"source": "finfocus", "version": "0.5.0"}
}

budget.name is global or <scope>:<scope_key>. For a forecasted alert, current holds the forecasted spend and percentage. Content-Type is application/json unless a configured header sets it.

  • Destinations are sent concurrently, each with a 10-second time limit.

  • URLs must use HTTPS. A URL built from a variable is checked after expansion. Redirects are never followed; a 3xx response is a failure.

  • A failure (error status, timeout, unset variable) prints a warning on stderr and never changes the command’s output or exit code:

    warning: slack notification for global budget (80% actual) failed: unexpected response status: status 500
  • Warnings and logs never contain a URL, a variable’s value, a secret header value, or a response body. A literal header value is treated as secret when it is at least 8 characters long or the header is Authorization, Proxy-Authorization, Cookie, or a name containing token, key, or secret. config get and config list show those values as [REDACTED], except a value that is only a ${NAME} reference.

  • Nothing about notifications is written to stdout, so --output json and --output ndjson stay parseable.

  • Results with mixed currencies skip budget evaluation, so no notification is sent.

Security: Project Config and Pull Requests

Section titled “Security: Project Config and Pull Requests”

A project config ($PROJECT/.finfocus/config.hujson) is committed, so a pull request can edit it. Three rules keep CI secrets out of a destination that a pull request controls:

  1. Only ${FINFOCUS_NOTIFY_*} variables expand, in any config. A reference such as ${AWS_SECRET_ACCESS_KEY} or ${GITHUB_TOKEN} fails validation and is never read.
  2. Destinations in a project config never expand variables. finfocus config validate (which also checks the resolved project file when run without --file), the cost commands, and overview --notify reject a ${...} reference there with a hint to move the destination to the global config. This covers a legacy .finfocus/config.yaml too. If one reaches a run, that destination is skipped with a warning.
  3. When FinFocus cannot find a home directory and falls back to ./.finfocus for its global config, that file is treated as a project config, because it may be committed.

A project-config destination with only literal values still sends. A pull request can therefore point the budget event at a host it chooses. The event holds budget figures only, but do not set FINFOCUS_NOTIFY on runs for untrusted pull requests.


The finfocus overview command shows budget status differently per output mode:

  • Interactive TUI: Budget footer with health badge loads asynchronously below the resource table. Press Enter on a resource for per-budget detail with forecasts and triggered alerts.
  • JSON (--output json): Full budget data in the top-level budgets array.
  • NDJSON (--output ndjson): Budgets are excluded — NDJSON is resource-scoped and budgets are stack-scoped.
  • Plain (--plain): Budget status is not rendered in the table. Use --exit-on-threshold for CI/CD enforcement.

See overview command — Budget Status for the complete visibility matrix and flag reference.


Common issues and solutions for budget configuration.

Symptoms:

  • Costs clearly exceed budget but no alert is shown
  • Exit code is 0 despite overage

Cause:

  • Mismatch between currency in config and cost data
  • Using actual alert type for projected costs (or vice versa)

Solution:

Check your currency matches your cloud provider data:

currency: USD # Ensure this matches plugin output

And ensure you’re using the right alert type. Use forecasted for cost projected commands.

Symptoms:

  • IDE highlights config properties in red
  • unknown property errors

Solution:

Ensure you have the correct schema directive and your indentation is correct:

# yaml-language-server: $schema=https://rshade.github.io/finfocus/schemas/config.json

Related Guides:

CLI Reference:

Configuration Reference:

Examples:


Last Updated: 2026-10-04 FinFocus Version: v0.3.4 Feedback: Open an issue to improve this guide