CI integration
Drop-in recipes for GitHub Actions, GitLab, Jenkins, Azure DevOps, and CircleCI.
Last updated October 25, 2026View source (v0.30.0)
This page gives copy-and-paste recipes for running Bomly in GitHub Actions, GitLab CI, Jenkins, Azure DevOps, CircleCI, and a pre-commit hook. Read it when you run a CI system other than GitHub pull requests, or when you want full control over the flags. For GitHub pull requests, Bomly Guard does this in one step.
Every recipe follows the same four steps:
- Install a pinned Bomly version.
- Run
bomly scan --enrich --audit --fail-on <severity>. - Upload the SBOM and SARIF files, even when the scan fails.
- Let exit code
2fail the build. See Exit codes.
| Platform | SARIF display | SBOM artifact | Cache |
|---|---|---|---|
| GitHub Actions | Security tab, through upload-sarif | upload-artifact | actions/cache on ~/.bomly/cache and ~/.cache/grype |
| GitLab CI | Not native | artifacts, plus reports:cyclonedx for the built-in viewer | Project-local directory, linked to the real cache locations |
| Jenkins | Warnings Next Generation plugin | archiveArtifacts | Workspace |
| Azure DevOps | SARIF SAST Scans Tab extension | PublishBuildArtifacts | Pipeline caching |
| CircleCI | Not native | store_artifacts | save_cache on ~/.bomly/cache and ~/.cache/grype |
Install
Prefer a package-manager install where the runner image supports it. Otherwise use the install script with a pinned version. Never rely on latest in CI:
curl -fsSL https://bomly.dev/install.sh | BOMLY_VERSION=v0.28.3 sh
GitHub Actions
Scan on pushes and pull requests, upload SARIF to the Security tab, and keep the SBOMs as artifacts:
name: Bomly
on:
pull_request:
push:
branches: [main]
jobs:
bomly:
runs-on: ubuntu-latest
permissions:
security-events: write
contents: read
steps:
- uses: actions/checkout@v5
- name: Install Bomly
run: curl -fsSL https://bomly.dev/install.sh | BOMLY_VERSION=v0.28.3 sh
- name: Scan
run: |
bomly scan --enrich --audit --fail-on high \
--format sarif \
-o spdx=sbom.spdx.json \
-o cyclonedx=sbom.cdx.json \
> bomly.sarif
- name: Upload SARIF
if: success() || failure()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: bomly.sarif
- name: Upload SBOMs
if: success() || failure()
uses: actions/upload-artifact@v4
with:
name: sbom
path: sbom.*.json
if: success() || failure() runs the uploads even when the scan exits 2.
Diff against the base branch on pull requests
To audit only what a pull request changed, fetch the base branch and run bomly diff:
- name: Diff against main
if: github.event_name == 'pull_request'
run: |
git fetch origin ${{ github.base_ref }}:base
bomly diff --base base --head HEAD \
--enrich --audit --fail-on high \
--json > bomly-diff.json
Only the packages the PR touched are audited, so findings elsewhere never fail the job. The diff command page explains which findings gate.
Cache enrichment data
Bomly keeps enrichment responses under ~/.bomly/cache and the bundled Grype vulnerability database under the operating system's cache directory (~/.cache/grype/db on Linux). Caching both between runs cuts enrichment time from minutes to seconds:
- name: Cache Bomly enrichment data
uses: actions/cache@v4
with:
path: |
~/.bomly/cache
~/.cache/grype
key: bomly-${{ runner.os }}-${{ hashFiles('**/go.sum', '**/package-lock.json', '**/pom.xml') }}
restore-keys: bomly-${{ runner.os }}-
Cache lifetimes are listed in Matchers. A scheduled nightly run on main keeps the cache warm for pull request jobs.
Pull request reviews with Bomly Guard
For GitHub pull requests, the Bomly Guard action wraps bomly diff --enrich --audit in one step and posts the result as a PR comment. Its quick start, required permissions, and inputs are on that page. The action installs Bomly, not your package managers; add setup steps such as actions/setup-node before it when your project needs them (see Package manager setup).
GitLab CI
Scan, keep the SBOMs as artifacts, and show the CycloneDX SBOM in GitLab's dependency view:
bomly:
image: ubuntu:24.04
stage: test
variables:
XDG_CACHE_HOME: "$CI_PROJECT_DIR/.bomly-cache" # Grype database goes to .bomly-cache/grype/db
before_script:
- apt-get update && apt-get install -y curl ca-certificates
- curl -fsSL https://bomly.dev/install.sh | BOMLY_VERSION=v0.28.3 sh
- mkdir -p "$CI_PROJECT_DIR/.bomly-cache/bomly" ~/.bomly
- ln -sfn "$CI_PROJECT_DIR/.bomly-cache/bomly" ~/.bomly/cache # enrichment responses
script:
- |
bomly scan --enrich --audit --fail-on high \
--format text \
-o spdx=sbom.spdx.json \
-o cyclonedx=sbom.cdx.json
artifacts:
when: always
paths:
- sbom.spdx.json
- sbom.cdx.json
reports:
cyclonedx: sbom.cdx.json
cache:
key:
files:
- "**/go.sum"
- "**/package-lock.json"
- "**/pom.xml"
paths:
- .bomly-cache
GitLab caches only paths inside the project directory, while Bomly writes enrichment responses to ~/.bomly/cache and the bundled Grype database to the operating system's cache directory. The recipe points both at .bomly-cache inside the project: XDG_CACHE_HOME moves the Grype database there, and the symlink moves Bomly's own cache there. Only the OSV, KEV, and Scorecard matchers have a cache_dir setting of their own; see the Config reference.
Jenkins
Scan, archive the files, and show findings on the build page through the Warnings Next Generation plugin:
pipeline {
agent any
stages {
stage('Bomly') {
steps {
sh '''
curl -fsSL https://bomly.dev/install.sh | BOMLY_VERSION=v0.28.3 sh
bomly scan --enrich --audit --fail-on high \
--format sarif \
-o spdx=sbom.spdx.json \
-o cyclonedx=sbom.cdx.json \
> bomly.sarif
'''
}
post {
always {
archiveArtifacts artifacts: 'bomly.sarif, sbom.*.json', fingerprint: true
recordIssues tools: [sarif(pattern: 'bomly.sarif')]
}
}
}
}
}
Azure DevOps
Scan and publish the SARIF file. The free SARIF SAST Scans Tab extension shows the results on the build page:
steps:
- script: |
curl -fsSL https://bomly.dev/install.sh | BOMLY_VERSION=v0.28.3 sh
bomly scan --enrich --audit --fail-on high --format sarif > bomly.sarif
displayName: 'Bomly scan'
- task: PublishBuildArtifacts@1
condition: succeededOrFailed()
inputs:
pathToPublish: bomly.sarif
artifactName: bomly-sarif
CircleCI
Scan with the enrichment cache restored between runs:
version: 2.1
jobs:
bomly:
docker:
- image: cimg/base:stable
steps:
- checkout
- restore_cache:
keys:
- bomly-cache-v2-{{ checksum "go.sum" }}
- bomly-cache-v2-
- run:
name: Install and scan
command: |
curl -fsSL https://bomly.dev/install.sh | BOMLY_VERSION=v0.28.3 sh
bomly scan --enrich --audit --fail-on high \
--format text \
-o spdx=sbom.spdx.json \
-o cyclonedx=sbom.cdx.json
- save_cache:
key: bomly-cache-v2-{{ checksum "go.sum" }}
paths:
- ~/.bomly/cache
- ~/.cache/grype
- store_artifacts:
path: sbom.spdx.json
- store_artifacts:
path: sbom.cdx.json
Pre-commit hook
Run a scan before every push:
# .pre-commit-config.yaml
- repo: local
hooks:
- id: bomly
name: bomly scan
entry: bomly scan --enrich --audit --fail-on critical --format text
language: system
pass_filenames: false
stages: [pre-push]
pre-push keeps commits fast. Choose the --fail-on level that fits your team.
Recommendations
- Pin the Bomly version. Use a tagged release or a package-manager version, never
latest. - Cache
~/.bomly/cacheand the Grype database directory across runs. The cache lifetimes make this safe. - Upload the SBOM even when the scan fails. It is a release artifact in its own right.
- Use
bomly diffon pull requests, so only the packages the PR changes are audited.
See also
- Exit codes — what each exit code means in CI
- Output formats — SARIF, JSON, and SBOM details
- Auditors —
--fail-onand the policy flags