GitLab CI
Run Infracost in GitLab CI to see cloud cost estimates and FinOps best practices in merge requests.
We recommend using the GitLab App where possible — it's simpler to set up and faster to run.
Quick start
-
Install the Infracost CLI and run
infracost auth loginto get a free API key. Retrieve it withinfracost configure get api_key. -
Add a project CI/CD variable called
INFRACOST_API_KEYwith your API key. Mark it masked, and untick Protect variable so it works on all merge requests. -
Add a Personal access token (or a Group / Project access token on premium / self-managed) with the
apiscope and Maintainer role, so it can read and write merge request comments. Add it as a masked, unprotected CI/CD variable calledGITLAB_TOKEN. -
Create a
.gitlab-ci.ymlfile in your repo with the following content:
variables:
TF_ROOT: RELATIVE/PATH/TO/TERRAFORM/CODE
stages:
# Runs the Infracost CLI and posts the merge request comment
- infracost:merge-request-checks
# These stages are needed when using Infracost Cloud
- infracost:default-branch-update
- infracost:merge-request-status-update
# Run Infracost on merge requests to check costs and policies
infracost:merge-request-checks:
stage: infracost:merge-request-checks
image:
name: infracost/infracost:ci-0.10
entrypoint: ['']
script:
# Clone the base branch of the merge request into a temp directory
- git clone $CI_REPOSITORY_URL --branch=$CI_MERGE_REQUEST_TARGET_BRANCH_NAME --single-branch /tmp/base
# Generate the cost baseline from the comparison branch
- |
infracost breakdown --path=/tmp/base/${TF_ROOT} \
--format=json \
--out-file=infracost-base.json
# Generate the diff
- |
infracost diff --path=${TF_ROOT} \
--compare-to=infracost-base.json \
--format=json \
--out-file=infracost.json
# Post a comment on the merge request, updating it on each run
- |
infracost comment gitlab --path=infracost.json \
--repo=$CI_PROJECT_PATH \
--merge-request=$CI_MERGE_REQUEST_IID \
--gitlab-server-url=$CI_SERVER_URL \
--gitlab-token=$GITLAB_TOKEN \
--behavior=update
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
# Run Infracost on default branch and update Infracost Cloud
infracost:default-branch-update:
stage: infracost:default-branch-update
image:
name: infracost/infracost:ci-0.10
entrypoint: ['']
script:
- |
infracost breakdown --path=${TF_ROOT} \
--format=json \
--out-file=infracost.json
infracost upload --path=infracost.json
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
# Set the MR status to Merged in Infracost Cloud
infracost:merge-request-status-update:
stage: infracost:merge-request-status-update
image:
name: infracost/infracost:ci-0.10
entrypoint: ['']
before_script:
- if [[ ${CI_COMMIT_MESSAGE} =~ ${PATTERN} ]];
then MR_ID=${BASH_REMATCH[1]};
else echo "Unable to extract Merge Request ID"; exit 1;
fi;
script:
- |
curl \
--request POST \
--header "Content-Type: application/json" \
--header "X-API-Key: ${INFRACOST_API_KEY}" \
--data "{ \"query\": \"mutation {updatePullRequestStatus( url: \\\"${CI_PROJECT_URL}/merge_requests/${MR_ID}\\\", status: MERGED )}\" }" \
"https://dashboard.api.infracost.io/graphql";
variables:
PATTERN: 'See merge request.+?!([0-9]+)'
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_COMMIT_TITLE =~ /^Merge branch/
- Open a test merge request — see these steps for what to expect.
For more elaborate setups (multi-project configs, private modules, Slack notifications, plan-JSON workflows), see the examples in the source repo.
Troubleshooting
HTTP 403 when posting comments
Check that the correct $GITLAB_TOKEN is being used — GitLab's variable precedence rules sometimes select the wrong one. You can also verify the token has the right access:
export GITLAB_TOKEN=mytoken
export CI_SERVER_URL=https://gitlab.com
export CI_PROJECT_PATH=my-org%2Fmy-repo # URL-encoded
export CI_MERGE_REQUEST_IID=123
curl -i -X POST \
--header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
--data-urlencode "body=testcomment" \
$CI_SERVER_URL/api/v4/projects/$CI_PROJECT_PATH/merge_requests/$CI_MERGE_REQUEST_IID/notes
A 401 / 403 response means the token is the issue.
Windows: INFRACOST_API_KEY not set
When using a Windows GitLab Runner, the variable syntax differs by shell — $INFRACOST_API_KEY in Bash, $env:INFRACOST_API_KEY in PowerShell. See GitLab's variables docs.
Windows: ParserError on multiline scripts
PowerShell doesn't accept Bash-style line continuations. Convert multi-line CLI commands to one-liners, e.g.:
- |
infracost breakdown --path=/tmp/base/${TF_ROOT} --format=json --out-file=infracost-base.json