Guides

CI/CD Integration

Overview

The LevelFour CLI (l4) integrates into CI/CD pipelines to catch cost regressions before they reach production. Two key commands:

  • l4 estimate - Estimate monthly cost of Terraform infrastructure
  • l4 diff - Show cost impact of infrastructure changes vs. a baseline

Both commands support a --fail-above threshold that exits with code 2 when exceeded, letting you gate deployments on cost.

Installing the CLI

Download the latest release binary in your CI job:

curl -sSL https://github.com/LevelFourAI/levelfour-cli/releases/latest/download/l4_linux_amd64.tar.gz | tar xz
sudo mv l4 /usr/local/bin/

Authentication

Pass your API token via the LEVELFOUR_TOKEN environment variable or the --token flag:

export LEVELFOUR_TOKEN="l4_live_..."

l4 whoami

For CI, store the token as a secret and pass it via environment variable. The CLI resolves tokens in this order:

  1. --token flag (highest priority)
  2. LEVELFOUR_TOKEN environment variable
  3. System keychain (interactive use only)

Cost Estimation

Estimate the monthly cost of your Terraform infrastructure:

l4 estimate ./infra/

Example output:

Name                                          Quantity  Unit       Monthly Cost

 aws_instance.bastion
 ├─ Instance usage (Linux, t4g.micro)              730  hours            $6.13
 └─ root_block_device
     └─ Storage (General Purpose, gp3)              30  GB               $2.40

 aws_ecs_service.api
 └─ Fargate vCPU                                  0.25  vCPU             $7.39

 aws_db_instance.this
 └─ Instance usage (db.t4g.micro)                  730  hours           $12.41

 Total                                                                 $52.29

CI Gate with Threshold

Exit with code 2 if the estimated monthly cost exceeds a threshold:

l4 estimate ./infra/ --fail-above 500 -q
echo $?
0

With a lower threshold:

l4 estimate ./infra/ --fail-above 1 -q
echo $?
Cost delta $52.29 exceeds threshold $1.00
2

The -q (quiet) flag suppresses the table output and communicates only via exit code.

Save a Snapshot

Save the resource snapshot for later comparison with l4 diff:

l4 estimate ./infra/ --out-file baseline.json

Output Formats

FormatFlagUse Case
Table--format tableTerminal output (default)
JSON--format jsonProgrammatic consumption
GitHub Comment--format github-commentMarkdown for PR comments

The github-comment format outputs a Markdown table suitable for PR comments:

## Cost Estimate

| Module | Resource | Type | Cost/mo | Delta |
|--------|----------|------|---------|-------|
|  | + bastion | aws_instance | $8.53 | +$8.53 |
|  | + api | aws_ecs_service | $7.39 | +$7.39 |
| `module.db` | + this | aws_db_instance | $13.98 | +$13.98 |

Cost Diff

Show how Terraform changes affect monthly costs by comparing against a baseline.

Git-Based Diff (Default)

By default, l4 diff compares the current branch against the git merge-base with main or master:

l4 diff ./infra/

Custom Base Branch

Compare against a specific git ref:

l4 diff --base develop ./infra/

Snapshot-Based Diff

Compare against a previously saved snapshot:

l4 diff baseline.json ./infra/

CI Gate with Threshold

Exit with code 2 if the cost delta exceeds a threshold:

l4 diff ./infra/ --fail-above 100 --format github-comment

GitHub Actions

Cost Estimate on Every PR

name: Cost Estimate
on:
  pull_request:
    paths:
      - 'infra/**'

jobs:
  cost-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install LevelFour CLI
        run: |
          curl -sSL https://github.com/LevelFourAI/levelfour-cli/releases/latest/download/l4_linux_amd64.tar.gz | tar xz
          sudo mv l4 /usr/local/bin/

      - name: Estimate costs
        id: estimate
        env:
          LEVELFOUR_TOKEN: ${{ secrets.LEVELFOUR_TOKEN }}
        run: |
          l4 estimate ./infra/ --format github-comment > cost-estimate.md

      - name: Comment on PR
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require('fs');
            const body = fs.readFileSync('cost-estimate.md', 'utf8');
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: body,
            });

Cost Diff with Threshold Gate

name: Cost Diff
on:
  pull_request:
    paths:
      - 'infra/**'

jobs:
  cost-diff:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Install LevelFour CLI
        run: |
          curl -sSL https://github.com/LevelFourAI/levelfour-cli/releases/latest/download/l4_linux_amd64.tar.gz | tar xz
          sudo mv l4 /usr/local/bin/

      - name: Cost diff
        id: diff
        env:
          LEVELFOUR_TOKEN: ${{ secrets.LEVELFOUR_TOKEN }}
        run: |
          l4 diff ./infra/ --format github-comment --fail-above 100 > cost-diff.md || echo "exit_code=$?" >> $GITHUB_OUTPUT

      - name: Comment on PR
        if: always()
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require('fs');
            const body = fs.readFileSync('cost-diff.md', 'utf8');
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: body,
            });

      - name: Fail if over threshold
        if: steps.diff.outputs.exit_code == '2'
        run: exit 1

GitLab CI

cost-estimate:
  stage: validate
  image: ubuntu:latest
  before_script:
    - curl -sSL https://github.com/LevelFourAI/levelfour-cli/releases/latest/download/l4_linux_amd64.tar.gz | tar xz
    - mv l4 /usr/local/bin/
  script:
    - l4 estimate ./infra/ --fail-above 500 --format table
  variables:
    LEVELFOUR_TOKEN: $LEVELFOUR_TOKEN
  rules:
    - changes:
        - infra/**

cost-diff:
  stage: validate
  image: ubuntu:latest
  before_script:
    - curl -sSL https://github.com/LevelFourAI/levelfour-cli/releases/latest/download/l4_linux_amd64.tar.gz | tar xz
    - mv l4 /usr/local/bin/
  script:
    - l4 diff ./infra/ --fail-above 100 --format table
  variables:
    LEVELFOUR_TOKEN: $LEVELFOUR_TOKEN
    GIT_DEPTH: 0
  rules:
    - changes:
        - infra/**

Estimate Flags

FlagDefaultDescription
--formattableOutput format: table, json, github-comment
--out-file-Save resource snapshot to file
--fail-above0Exit code 2 if monthly cost exceeds threshold
--regionus-east-1AWS region for pricing
--gcp-regionauto-detectGCP region for pricing
--azure-regionauto-detectAzure region for pricing
--var-file-Terraform variable files (repeatable)
--var-Terraform variables as key=value (repeatable)
--download-modulestrueDownload and resolve remote Terraform modules
--max-resources500Maximum number of resources to include

Diff Flags

FlagDefaultDescription
--formattableOutput format: table, json, github-comment
--fail-above0Exit code 2 if monthly delta exceeds threshold
--baseauto-detectGit ref to diff against (defaults to main/master merge-base)
--regionus-east-1AWS region for pricing
--gcp-regionauto-detectGCP region for pricing
--azure-regionauto-detectAzure region for pricing
--var-file-Terraform variable files (repeatable)
--var-Terraform variables as key=value (repeatable)
--download-modulestrueDownload and resolve remote Terraform modules
--max-resources500Maximum number of resources to include

Exit Codes

CodeMeaning
0Success
1General error
2--fail-above threshold exceeded
4Authentication required
130Interrupted (Ctrl+C)