GitHub Actions
Run Infracost in GitHub Actions to post cloud cost estimates and FinOps best practices on every pull request.
The GitHub App needs no pipeline configuration and is the recommended path. Use this page when you need the estimate to run inside your own pipeline.
1. Add your CLI token
Add your Infracost CLI token as a repository secret called INFRACOST_CLI_AUTHENTICATION_TOKEN.
The built-in GITHUB_TOKEN posts the comment, so there is no second token to create. It still has to be mapped into the job's env:, as step 2 does.
2. Add the workflow
name: Infracost
on: [pull_request]
jobs:
infracost:
runs-on: ubuntu-latest
container: ghcr.io/infracost/ci:2
permissions:
contents: read
pull-requests: write
env:
INFRACOST_CLI_AUTHENTICATION_TOKEN: ${{ secrets.INFRACOST_CLI_AUTHENTICATION_TOKEN }}
# Actions exposes this only as an expression, never as an environment
# variable, and `diff` reads it from the environment.
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.base.sha }}
path: base
- uses: actions/checkout@v4
with:
# The default ref on a pull_request event is the merge commit, whose
# SHA and author are not the contributor's.
ref: ${{ github.event.pull_request.head.sha }}
path: head
- run: infracost-ci diff --base-path base --head-path head
That is the whole integration. diff takes two checkouts of the same repository, computes the cost difference, posts or updates the pull request comment, and uploads the run to Infracost Cloud.
The repository, pull request number, branches and run id all come from GitHub's own variables, so there is nothing else to wire up. No Terraform binary and no plan file: the parsers read your infrastructure code directly.
pull-requests: write is what lets GITHUB_TOKEN comment. Without the permission the estimate still runs and uploads, and only the comment fails. Without the GITHUB_TOKEN line itself, diff exits 1 before it scans anything.
The baseline and status workflows below need neither: they post no comment.
The image entrypoint is infracost-ci, but a container: job replaces it with its own shell, which is why the step names the binary rather than passing a subcommand to the image.
3. Track your default branch
Run a second workflow on pushes to your default branch so Infracost Cloud keeps baseline costs and issues current. This populates Issue Explorer and Visibility → Repos.
name: Infracost baseline
on:
push:
branches: [main]
jobs:
infracost:
runs-on: ubuntu-latest
container: ghcr.io/infracost/ci:2
env:
INFRACOST_CLI_AUTHENTICATION_TOKEN: ${{ secrets.INFRACOST_CLI_AUTHENTICATION_TOKEN }}
steps:
- uses: actions/checkout@v4
- run: infracost-ci scan --path .
scan takes a single directory, unlike diff.
4. Report merged pull requests
Infracost Cloud calculates cost prevention (money saved by changes made before merging) from merged pull requests.
name: Infracost status
on:
pull_request:
types: [closed]
jobs:
status:
if: github.event.pull_request.merged
runs-on: ubuntu-latest
container: ghcr.io/infracost/ci:2
env:
INFRACOST_CLI_AUTHENTICATION_TOKEN: ${{ secrets.INFRACOST_CLI_AUTHENTICATION_TOKEN }}
steps:
- uses: actions/checkout@v4
- run: infracost-ci status --status MERGED
To also report pull requests closed without merging, drop the job-level if: and pick the status per run:
- run: infracost-ci status --status "${STATUS}"
env:
STATUS: ${{ github.event.pull_request.merged && 'MERGED' || 'CLOSED' }}
GitHub Enterprise Server
Nothing extra is needed: the API URL is derived from the repository URL.
Override it with --github-api-url if yours differs. That flag wants the base server URL, the shape of GITHUB_SERVER_URL, not GITHUB_API_URL: the latter ends /api/v3, and /api/graphql is appended to whatever it is given.
Guardrails and blocking
Guardrails and policies evaluate on every run. When one blocks, the comment posts first and then diff exits 1, so the check goes red with the reason already on the pull request. An unblock from the dashboard takes effect on the next run.
A red check does not stop a merge on its own. To block one, mark the job required: Settings → Branches → the branch ruleset → Require status checks to pass, and add infracost.
Your code stays local
Code is parsed inside the runner. Only the cost estimate and the pull request metadata reach Infracost Cloud.