Infracost logo

Backstage Infracost Plugin

Created by VeeCode Platform

VeeCode Infracost brings cloud cost awareness into your Backstage. It reads estimates produced by Infracost for your Terraform projects and shows them in a clean view inside the portal. You see projected totals for your provider. You can explore how each resource contributes to the bill. This gives your team a shared source of truth for cost during everyday work.

The plugin adds a dedicated Infracost page to your entities. It presents a high level summary with charts. You can drill into cost components for each resource to understand drivers. The goal is to make costs visible where engineers already spend time so you do not need to jump between tools.

Common use cases include design reviews for new infra, pull request checks for risky changes, and routine cost hygiene. Teams use it to spot expensive instance types early, compare options, and discuss trade offs with data. It suits self hosted Backstage setups that provision with Terraform. If your catalog tracks clusters, services, or databases, the plugin can surface their estimated monthly spend and the breakdown that supports it. This helps FinOps work feel like part of normal delivery rather than an afterthought.

Installation Instructions

These instructions apply to self-hosted Backstage only.

Install the frontend plugin

  1. Add the package to your app

    If you use yarn three

    Copy
    yarn workspace app add @veecode-platform/backstage-plugin-infracost

    If you use other yarn versions

    Copy
    yarn add --cwd packages/app @veecode-platform/backstage-plugin-infracost
  2. Add the Infracost tab to your entity page

    Edit packages/app/src/components/catalog/EntityPage.tsx

    Copy
    // other imports
    import { InfracostOverviewPage, isInfracostAvailable } from '@veecode-platform/backstage-plugin-infracost';
    import { EntityLayout } from '@backstage/plugin-catalog';
    // import your existing pages too
    
    const clusterPage = (
      <EntityLayout>
        <EntityLayout.Route path="/" title="Overview">
          <ClusterOverviewPage />
        </EntityLayout.Route>
    
        {/* other routes */}
    
        <EntityLayout.Route if={isInfracostAvailable} path="/infracost" title="Infracost">
          <InfracostOverviewPage />
        </EntityLayout.Route>
      </EntityLayout>
    );
    
    export default clusterPage;

    You can add the route to any entity page you prefer. Cluster is an example.

Install the backend plugin new backend system

You need the Infracost backend plugin from VeeCode. The exact package name and module export come from the backend readme. The steps below show where to wire it into the new backend system.

  1. Add the backend package

    Copy
    yarn add --cwd packages/backend @veecode-platform/backstage-plugin-infracost-backend
  2. Register the backend module

    Edit packages/backend/src/index.ts

    Copy
    import { createBackend } from '@backstage/backend-defaults';
    // Replace this import with the module export from the backend plugin readme
    // Some plugins export a backend module function that you pass to backend.add
    import { infracostModule } from '@veecode-platform/backstage-plugin-infracost-backend';
    
    const backend = createBackend();
    
    // Add other modules already in your app here
    
    // Register Infracost backend module
    backend.add(infracostModule());
    
    backend.start();

    If the backend plugin exports a different helper name, use that. Keep the registration in the main backend index so it loads on start.

Install the backend plugin legacy backend system

If your app still uses the legacy backend with express routers, wire the plugin like this.

  1. Add the backend package

    Copy
    yarn add --cwd packages/backend @veecode-platform/backstage-plugin-infracost-backend
  2. Create the plugin router

    Create packages/backend/src/plugins/infracost.ts

    Copy
    import { PluginEnvironment } from '../types';
    // Replace the import below with the correct createRouter or factory from the backend plugin readme
    import { createRouter } from '@veecode-platform/backstage-plugin-infracost-backend';
    
    export default async function createPlugin(env: PluginEnvironment) {
      return await createRouter({
        logger: env.logger,
        config: env.config,
        discovery: env.discovery,
        reader: env.reader,
        database: env.database,
        permissions: env.permissions,
      });
    }
  3. Mount the router

    Edit packages/backend/src/index.ts

    Copy
    import infraRouter from './plugins/infracost';
    
    async function main() {
      const env = useHotMemoize(module, () => createEnv('plugin'));
      const apiRouter = Router();
    
      // other routers
    
      apiRouter.use('/infracost', await infraRouter(env));
    
      // start service with apiRouter
    }
    
    main().catch(err => {
      process.exit(1);
    });

    If the plugin readme shows a different mount path, use that path. The example uses /infracost under the main api router.

Configure your catalog entities

You need two things in your catalog

  1. The main component entity needs an annotation

    Add infracost project to the component. The value must match the component name that holds the estimate.

    Copy
    apiVersion: veecode.backstage.io/v1alpha1
    kind: Cluster
    metadata:
      name: cluster-ec2
      annotations:
        github.com/project-slug: test/cluster-ec2
        backstage.io/techdocs-ref: dir:.
        infracost/project: cluster-ec2
    spec:
      type: ec2
      lifecycle: experimental
      owner: group:default/admin
  2. An Infracost entity that references the estimate file

    You can add it manually or automate it with a workflow. Both paths are below.

Manual estimate generation

  1. Generate the estimate JSON at the project root after you have a terraform plan

    Copy
    infracost breakdown --path plan_cache_cli.json --format json --out-file infracost-base.json
  2. Move the estimate and create the Infracost entity

    Create a folder named .content in the repo root. Move infracost-base.json into .content. Then create .content/infracost.yaml

    Copy
    apiVersion: veecode.backstage.io/v1alpha1
    kind: Infracost
    metadata:
      name: cluster-ec2
      annotations:
        backstage.io/techdocs-ref: dir:.
    spec:
      type: FinOps
      lifecycle: experimental
      owner: group:default/admin
      estimate:
        $text: ./infracost-base.json
  3. Move your main entity into .content and fix techdocs path if needed

    If you move catalog-info.yaml into .content and rename it to match your kind, update techdocs to point one level up

    Copy
    apiVersion: veecode.backstage.io/v1alpha1
    kind: Cluster
    metadata:
      name: cluster-ec2
      annotations:
        github.com/project-slug: test/cluster-ec2
        backstage.io/techdocs-ref: dir:..
        infracost/project: cluster-ec2
    spec:
      type: ec2
      lifecycle: experimental
      owner: group:default/admin
  4. Add a location at the repo root that points to every entity file inside .content

    Create catalog-info.yaml at the repo root

    Copy
    apiVersion: backstage.io/v1alpha1
    kind: Location
    metadata:
      name: cluster-ec2-location
      description: Importing components
    spec:
      targets:
        - ./.content/*.yaml

Automated estimate with GitHub Actions

This workflow runs terraform plan then creates infracost-base.json. It writes the Infracost entity file and commits artifacts into .content.

Create .github/workflows/infracost-estimate.yml

Copy
name: infracost-estimate

on:
  push:
    branches:
      - "*"
      - "*/*"
      - "**"
  workflow_dispatch: 
env:
  PATH_INFRACOST: ./.content

jobs:
  infracost:
    name: Infracost
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: write

    steps:
      - uses: hashicorp/setup-terraform@v2
      - name: Setup Infracost
        uses: infracost/actions/setup@v2
        with:
          api-key: ${{ secrets.INFRACOST_API_KEY }}
          path: |
            .terraform/**
            .terraform.lock.hcl
            plan_cache.json
          key: terraform-lock-${{ steps.extract_branch.outputs.branch }}

      - name: Checkout base branch
        uses: actions/checkout@v3
        with:
          ref: '${{ github.event.pull_request.base.ref }}'
      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY }}
          aws-region: ${{ secrets.AWS_REGION }}
      - name: Terraform Init
        id: init
        run: |
             terraform init
             terraform plan -no-color -out plan_cache.json 
      # Generate Infracost JSON file as the baseline.
      - name: Generate Infracost cost estimate baseline
        run: |
          infracost breakdown --show-skipped --path plan_cache.json

      - name: Generate Infracost cost estimate Json
        run: |
          infracost breakdown --path plan_cache.json --format json --out-file ${{ env.PATH_INFRACOST }}/infracost-base.json

      - name: Generate component infracost.yaml
        run: |
          echo 'apiVersion: veecode.backstage.io/v1alpha1
          kind: Infracost
          metadata:
            name: cluster-ec2  # component name
            annotations:
             backstage.io/techdocs-ref: dir:.
          spec:
            type: FinOps
            lifecycle: experimental
            owner: "group:default/admin"
            estimate:
             $text: ./infracost-base.json' > ${{ env.PATH_INFRACOST }}/infracost.yaml

      - name: Publish generated artifacts
        uses: stefanzweifel/git-auto-commit-action@v5
        with:
          file_pattern: "${{ env.PATH_INFRACOST }}"
          commit_user_name: ${{ secrets.GH_USERNAME }}
          commit_user_email: ${{ secrets.GH_EMAIL }}
          commit_author: ${{ secrets.GH_USERNAME }}<${{ secrets.GH_EMAIL }}>
          commit_message: "Publish infracost estimate"
          push_options: '--force'

If you choose a different folder, update the location targets and the PATH_INFRACOST variable.

What the backend stores

Once the backend plugin is running and the Infracost entity points to infracost-base.json, the backend processor persists the data in the database. Then the UI tab reads it and shows the cost estimate for the selected entity.

Changelog

This changelog is produced from commits made to the Infracost plugin since a year ago, and based on the code located here. It may not contain information about all commits. Releases and version bumps are intentionally omitted. This changelog is generated by AI.

Features

  • Add shared icon components. Update all icon usages. merged 3 months ago

Bug fixes

  • Add overflow scroll to tables. Prevent cut off on small screens. merged 11 months ago
  • Add hide or show toggle in chart preview. merged 11 months ago

Compatibility

  • Bump Backstage core to v1.35.0 from v1.32.4. merged 8 months ago

Maintenance

  • Switch to the MUI icons material package. Remove the react icons package. merged 3 months ago
  • Update to Yarn v4. Include an asset handling fix. merged 11 months ago

Breaking changes

  • None

Set up Backstage in minutes with Roadie