SBOM Generation in CI/CD Pipelines - GitHub Actions, GitLab CI, Bitbucket

Learn how to automate SBOM generation in CI/CD pipelines. Complete guide with GitHub Actions, GitLab CI, Bitbucket Pipelines, and attestation examples.

Why Generate SBOMs in CI/CD?

Generating SBOMs in your CI/CD pipeline provides several advantages:

  1. Consistency - Every build produces an SBOM with the same tools and configuration
  2. Automation - No manual steps to forget
  3. Attestation - Link SBOMs cryptographically to specific builds
  4. Compliance - Automatically meet requirements like EO 14028
  5. 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

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

  1. Generate on every build - SBOMs should be artifacts of your CI/CD pipeline
  2. Store as build artifacts - Keep SBOMs alongside your build outputs
  3. Use attestations - Cryptographically link SBOMs to builds
  4. Version your SBOMs - Include build numbers or commit hashes
  5. Fail on errors - Don’t ship if SBOM generation fails
  6. Scan immediately - Integrate vulnerability scanning with SBOM generation
  7. 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-lockfile or 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:

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.