Jenkins
Run Infracost in Jenkins to post cloud cost estimates and FinOps best practices on every pull request.
Jenkins is VCS-agnostic: it names no host, so the provider and repository URL are yours to set. Everything else comes from a multibranch pipeline's CHANGE_* variables.
1. Add the credentials
Add three Jenkins credentials:
| Credential ID | Value |
|---|---|
infracost-cli-token | Your Infracost CLI token |
github-token | Secret text. A token for the VCS your repository lives on |
git-credentials | Username with password. The same token as the password, for the git fetch |
Swap the last two for GitLab, Bitbucket or Azure DevOps credentials to match where the repository lives.
The last two hold the same token in different shapes, because they are consumed differently: infracost-ci reads a token from the environment, and git wants a username and a password. On a public repository you only need the first two.
2. Add the pipeline
pipeline {
agent {
docker {
image 'ghcr.io/infracost/ci:2'
// The plugin holds the container open with `cat`, so clear the entrypoint.
args '--entrypoint='
}
}
environment {
INFRACOST_CLI_AUTHENTICATION_TOKEN = credentials('infracost-cli-token')
GITHUB_TOKEN = credentials('github-token')
INFRACOST_VCS_PROVIDER = 'github'
INFRACOST_VCS_REPOSITORY_URL = 'https://github.com/ORG/REPO'
}
stages {
stage('Infracost') {
when { changeRequest() }
steps {
// An `sh` step does not inherit the checkout's credentials, so the
// fetch below needs them supplied.
withCredentials([gitUsernamePassword(credentialsId: 'git-credentials')]) {
sh '''
# The workspace is reused between builds, so clear old worktrees.
git worktree remove --force base || true
git worktree remove --force head || true
git worktree prune
git fetch origin "$CHANGE_TARGET"
git worktree add base FETCH_HEAD
git worktree add head HEAD
infracost-ci diff --base-path base --head-path head
'''
}
}
}
}
}
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. No Terraform binary and no plan file: the parsers read your infrastructure code directly.
Set INFRACOST_VCS_PROVIDER to github, gitlab, bitbucket or azure_repos, and the token variable to GITHUB_TOKEN, GITLAB_TOKEN, BITBUCKET_TOKEN or AZURE_DEVOPS_EXT_PAT to match.
Bitbucket users must also set INFRACOST_CI_PLATFORM = 'jenkins'. Any variable starting BITBUCKET_ makes detection guess Bitbucket Pipelines, which drops every CHANGE_* value, so the pull request number and branches come out empty and diff fails.
Four things are specific to Jenkins:
- The fetch needs credentials of its own. An
shstep does not inherit the ones the SCM checkout used, so on a private repositorygit fetch originfails withfatal: could not read Username for 'https://github.com'.withCredentials([gitUsernamePassword(...)])supplies them for the length of the block, using thegit-credentialsentry above. It needs the Git plugin. On a public repository, drop the wrapper and keep the baresh. - The base worktree points at
FETCH_HEAD. Jenkins checks out the pull request ref, so there is noorigin/$CHANGE_TARGETto point at. - Worktrees are cleared first. The workspace is reused between builds.
INFRACOST_VCS_REPOSITORY_URLis the web URL.GIT_URLis a clone URL and may carry credentials, so it is not a safe substitute.
If you set other INFRACOST_VCS_* variables on an earlier version, delete all but the two above. An environment value always beats an inferred one, and Groovy renders an unset env.CHANGE_BRANCH as the literal string null, which becomes the branch, or fails the run when INFRACOST_VCS_PULL_REQUEST_ID gets it.
Detection keys on JENKINS_URL, JENKINS_NODE_COOKIE or JENKINS_HOME, whichever the build has. JENKINS_URL is exported only when the Jenkins URL is set under Manage Jenkins → System, and JENKINS_HOME is controller state that a Docker agent or Kubernetes pod template does not see. Set INFRACOST_CI_PLATFORM = 'jenkins' to pin it when none of the three reach the build.
3. Track your default branch
Add a stage so Infracost Cloud keeps baseline costs and issues current. This populates Issue Explorer and Visibility → Repos.
stage('Infracost baseline') {
when { branch 'main' }
steps {
sh '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. Read the merge commit on the default branch:
stage('Infracost merged status') {
when { branch 'main' }
steps {
sh '''
PR=$(git log -1 --pretty=%B | grep -oE 'Merge pull request #[0-9]+' | grep -oE '[0-9]+$') || true
[ -n "$PR" ] && infracost-ci status --status MERGED --pr-number "$PR" || echo "no pull request in the commit message"
'''
}
}
Adjust the pattern to match your VCS: GitLab uses See merge request .+!([0-9]+), Azure Repos Merged PR ([0-9]+): and Bitbucket pull request #([0-9]+).
Fast-forward merges and customized merge commit templates break the pattern match. The pull request simply won't get merged status; nothing else fails.
Guardrails and blocking
Guardrails and policies evaluate on every run. When one blocks, the comment posts first and then diff exits 1, so the build goes red with the reason already on the pull request. An unblock from the dashboard takes effect on the next run.
A red build does not stop a merge on its own. Mark the check required on the VCS — on GitHub, Settings → Branches → the branch ruleset → Require status checks to pass.
Your code stays local
Code is parsed inside the agent. Only the cost estimate and the pull request metadata reach Infracost Cloud.