Why Generate SBOMs in CI/CD?
Generating SBOMs in your CI/CD pipeline provides several advantages:
- Consistency - Every build produces an SBOM with the same tools and configuration
- Automation - No manual steps to forget
- Attestation - Link SBOMs cryptographically to specific builds
- Compliance - Automatically meet requirements like EO 14028
- Auditability - Full traceability from source to deployed artifact
GitHub Actions
Basic SBOM Generation
The sbomify GitHub Action is a swiss army knife for SBOMs that automatically selects the best generation tool for your ecosystem, enriches the output with package metadata, and optionally augments it with your business information – all in one step.
---
name: Generate SBOM
on:
push:
branches: [main]
pull_request:
jobs:
sbom:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Generate SBOM
uses: sbomify/sbomify-action@master
env:
LOCK_FILE: package-lock.json
OUTPUT_FILE: sbom.cdx.json
ENRICH: true
UPLOAD: false
- name: Upload SBOM as artifact
uses: actions/upload-artifact@v4
with:
name: sbom
path: sbom.cdx.json
With sbomify Platform Upload
- name: Generate and Upload SBOM
uses: sbomify/sbomify-action@master
env:
TOKEN: ${{ secrets.SBOMIFY_TOKEN }}
COMPONENT_ID: my-component
LOCK_FILE: package-lock.json
OUTPUT_FILE: sbom.cdx.json
AUGMENT: true
ENRICH: true
GitHub Attestation (Recommended)
Use GitHub’s built-in attestation for tamper-proof SBOMs:
---
name: Build with SBOM Attestation
on:
push:
branches: [main]
permissions:
contents: read
id-token: write
attestations: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Build
run: npm ci && npm run build
- name: Generate SBOM
uses: sbomify/sbomify-action@master
env:
LOCK_FILE: package-lock.json
OUTPUT_FILE: sbom.cdx.json
COMPONENT_NAME: my-app
COMPONENT_VERSION: ${{ github.sha }}
ENRICH: true
UPLOAD: false
- name: Attest SBOM
uses: actions/attest-sbom@v1
with:
subject-path: './dist'
sbom-path: './sbom.cdx.json'
Docker Image with SBOM
---
name: Build and Push Container with SBOM
on:
push:
branches: [main]
permissions:
contents: read
packages: write
id-token: write
attestations: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
id: build
with:
context: .
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
sbom: true
provenance: true
- name: Generate additional SBOM
uses: sbomify/sbomify-action@master
env:
DOCKER_IMAGE: ghcr.io/${{ github.repository }}:${{ github.sha }}
OUTPUT_FILE: container-sbom.cdx.json
COMPONENT_NAME: ${{ github.repository }}
COMPONENT_VERSION: ${{ github.sha }}
ENRICH: true
UPLOAD: false
GitLab CI
Basic GitLab SBOM Generation
stages:
- build
- sbom
build:
stage: build
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/
generate-sbom:
stage: sbom
image: sbomifyhub/sbomify-action
variables:
LOCK_FILE: package-lock.json
OUTPUT_FILE: sbom.cdx.json
COMPONENT_NAME: my-app
COMPONENT_VERSION: $CI_COMMIT_TAG
UPLOAD: "false"
ENRICH: "true"
script:
- /sbomify.sh
artifacts:
paths:
- sbom.cdx.json
reports:
cyclonedx: sbom.cdx.json
GitLab with Dependency Scanning Integration
include:
- template: Security/Dependency-Scanning.gitlab-ci.yml
generate-sbom:
stage: test
image: sbomifyhub/sbomify-action
variables:
LOCK_FILE: package-lock.json
OUTPUT_FILE: gl-sbom-report.cdx.json
UPLOAD: "false"
ENRICH: "true"
script:
- /sbomify.sh
artifacts:
paths:
- gl-sbom-report.cdx.json
reports:
cyclonedx: gl-sbom-report.cdx.json
GitLab Container Scanning with SBOM
container-sbom:
stage: sbom
image: sbomifyhub/sbomify-action
services:
- docker:dind
variables:
DOCKER_HOST: tcp://docker:2376
DOCKER_TLS_CERTDIR: "/certs"
DOCKER_IMAGE: "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA"
OUTPUT_FILE: container-sbom.cdx.json
UPLOAD: "false"
ENRICH: "true"
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
script:
- /sbomify.sh
artifacts:
paths:
- container-sbom.cdx.json
Bitbucket Pipelines
Basic Bitbucket SBOM Generation
image: node:20
pipelines:
default:
- step:
name: Build
script:
- npm ci
- npm run build
artifacts:
- dist/**
- step:
name: Generate SBOM
script:
- pipe: docker://sbomifyhub/sbomify-action:latest
variables:
LOCK_FILE: package-lock.json
OUTPUT_FILE: sbom.cdx.json
COMPONENT_NAME: my-app
COMPONENT_VERSION: $BITBUCKET_TAG
UPLOAD: "false"
ENRICH: "true"
artifacts:
- sbom.cdx.json
For rolling releases, use $BITBUCKET_COMMIT instead of $BITBUCKET_TAG. See our SBOM versioning guide for best practices.
Bitbucket with Upload
pipelines:
default:
- step:
name: Generate and Upload SBOM
script:
- pipe: docker://sbomifyhub/sbomify-action:latest
variables:
TOKEN: $SBOMIFY_TOKEN
COMPONENT_ID: my-component
LOCK_FILE: package-lock.json
OUTPUT_FILE: sbom.cdx.json
ENRICH: "true"
Jenkins
Jenkins Pipeline with SBOM
pipeline {
agent {
docker {
image 'sbomifyhub/sbomify-action'
}
}
environment {
LOCK_FILE = 'package-lock.json'
OUTPUT_FILE = 'sbom.cdx.json'
COMPONENT_NAME = 'my-app'
COMPONENT_VERSION = "${env.GIT_TAG_NAME ?: env.GIT_COMMIT}"
UPLOAD = 'false'
ENRICH = 'true'
}
stages {
stage('Generate SBOM') {
steps {
sh '/sbomify.sh'
}
}
stage('Archive SBOM') {
steps {
archiveArtifacts artifacts: 'sbom.cdx.json'
}
}
}
}
Jenkins with Credentials
pipeline {
agent any
stages {
stage('Generate SBOM') {
steps {
withCredentials([string(credentialsId: 'sbomify-token', variable: 'TOKEN')]) {
docker.image('sbomifyhub/sbomify-action').inside {
sh '''
export COMPONENT_ID="my-component"
export LOCK_FILE="package-lock.json"
export OUTPUT_FILE="sbom.cdx.json"
export ENRICH="true"
export UPLOAD="true"
/sbomify.sh
'''
}
}
}
}
}
}
CircleCI
CircleCI SBOM Generation
version: 2.1
jobs:
generate-sbom:
docker:
- image: sbomifyhub/sbomify-action
steps:
- checkout
- run:
name: Generate SBOM
environment:
LOCK_FILE: package-lock.json
OUTPUT_FILE: sbom.cdx.json
COMPONENT_NAME: my-app
COMPONENT_VERSION: << pipeline.git.tag >>
UPLOAD: "false"
ENRICH: "true"
command: /sbomify.sh
- store_artifacts:
path: sbom.cdx.json
workflows:
build-and-sbom:
jobs:
- generate-sbom
Azure DevOps
Azure Pipelines SBOM Generation
trigger:
- main
pool:
vmImage: 'ubuntu-latest'
steps:
- checkout: self
- task: Docker@2
displayName: 'Generate SBOM'
inputs:
command: 'run'
arguments: |
-v $(Build.SourcesDirectory):/workspace
-w /workspace
-e LOCK_FILE=package-lock.json
-e OUTPUT_FILE=sbom.cdx.json
-e COMPONENT_NAME=my-app
-e COMPONENT_VERSION=$(Build.SourceVersion)
-e UPLOAD=false
-e ENRICH=true
sbomifyhub/sbomify-action
- publish: $(Build.SourcesDirectory)/sbom.cdx.json
artifact: sbom
Uploading to Dependency-Track
Integrate with OWASP Dependency-Track:
# GitHub Actions example
- name: Generate SBOM
uses: sbomify/sbomify-action@master
env:
LOCK_FILE: package-lock.json
OUTPUT_FILE: sbom.cdx.json
ENRICH: true
UPLOAD: false
- name: Upload to Dependency-Track
run: |
curl -X "POST" "https://dtrack.example.com/api/v1/bom" \
-H "X-Api-Key: ${{ secrets.DTRACK_API_KEY }}" \
-H "Content-Type: multipart/form-data" \
-F "project=${{ secrets.DTRACK_PROJECT_UUID }}" \
-F "[email protected]"
Multiple Language Projects
For projects with multiple lockfiles:
---
name: Generate Multiple SBOMs
on: [push]
jobs:
sbom:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- name: frontend
lock_file: frontend/package-lock.json
- name: backend
lock_file: backend/requirements.txt
- name: api
lock_file: api/go.mod
steps:
- uses: actions/checkout@v4
- name: Generate SBOM for ${{ matrix.name }}
uses: sbomify/sbomify-action@master
env:
LOCK_FILE: ${{ matrix.lock_file }}
OUTPUT_FILE: 'sbom-${{ matrix.name }}.cdx.json'
ENRICH: true
- uses: actions/upload-artifact@v4
with:
name: sbom-${{ matrix.name }}
path: sbom-${{ matrix.name }}.cdx.json
Best Practices
- Generate on every build - SBOMs should be artifacts of your CI/CD pipeline
- Store as build artifacts - Keep SBOMs alongside your build outputs
- Use attestations - Cryptographically link SBOMs to builds
- Version your SBOMs - Include build numbers or commit hashes
- Fail on errors - Don’t ship if SBOM generation fails
- Scan immediately - Integrate vulnerability scanning with SBOM generation
- Centralize storage - Upload to platforms like sbomify for management
Troubleshooting
Common Issues
SBOM generation fails in CI:
- Ensure all dependencies are installed (
npm ci,pip install, etc.) - Check that lockfiles are committed to the repository
Missing dependencies:
- Use
--frozen-lockfileor equivalent to ensure lockfile is respected - Verify the correct lockfile path
Authentication issues:
- For private registries, configure credentials in CI secrets
- Use service accounts with minimal permissions
Further Reading
Related blog posts:
- GitHub Action module with Attestation - SLSA build provenance attestation for SBOMs
- sbomify GitHub Action v0.3.0: Now Faster and Compatible with GitLab! - GitLab CI/CD support and performance improvements
Further Resources
For more SBOM tools and resources, see our SBOM Resources page, which includes tools for SBOM generation, distribution, and analysis across all supported languages.