The container image is the universal integration. Any platform that can run a container can run this, whether or not it has a dedicated page here.
The pattern
docker run --rm \
-v "$(pwd):/workspace" \
-e LOCK_FILE=requirements.txt \
-e OUTPUT_FILE=sbom.cdx.json \
-e ENRICH=true \
-e UPLOAD=false \
ghcr.io/sbomify/sbomify-actionTwo things matter:
- Mount your repository at
/workspace. That is the image’s working directory, so no-wis needed. Any other mount point works as long as-wpoints at it - including an existing-v "$(pwd):/github/workspace" -w /github/workspace, which keeps working unchanged. Keep the mount and the-win step: output paths resolve against the working directory, so a mount without a matching-wleaves your SBOM inside the container. - Pass configuration as environment variables. The image entrypoint is
sbomify-action, so no command is needed.
Podman works identically - substitute podman run.
Uploading
docker run --rm \
-v "$(pwd):/workspace" \
-e TOKEN="$SBOMIFY_TOKEN" \
-e COMPONENT_ID=your-component-id \
-e LOCK_FILE=requirements.txt \
-e AUGMENT=true \
-e ENRICH=true \
ghcr.io/sbomify/sbomify-actionTake the token from your platform’s secret store rather than hardcoding it. Passing secrets with -e exposes them in the process list on the host; if that matters, use --env-file with a file mode of 0600, or your container runtime’s secret mechanism.
Caching
Use a named volume so the license database survives between runs:
docker volume create sbomify-cache
docker run --rm \
-v "$(pwd):/workspace" \
-v sbomify-cache:/cache \
-e SBOMIFY_CACHE_DIR=/cache/sbomify \
-e SYFT_CACHE_DIR=/cache/syft \
-e SBOMIFY_TOOL_CACHE=/cache/runtimes \
-e GITHUB_TOKEN="$GITHUB_TOKEN" \
-e LOCK_FILE=requirements.txt \
-e OUTPUT_FILE=sbom.cdx.json \
-e ENRICH=true \
-e UPLOAD=false \
ghcr.io/sbomify/sbomify-actionGITHUB_TOKEN matters on every platform. License databases are downloaded from GitHub Releases, and unauthenticated requests are capped at 60 per hour per IP. When that is exceeded, enrichment degrades silently. See license database rate limits.
Container images
Scanning an image needs access to a Docker daemon:
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
-v "$(pwd):/workspace" \
-e DOCKER_IMAGE=my-app:latest \
-e OUTPUT_FILE=container-sbom.cdx.json \
-e ENRICH=true \
-e UPLOAD=false \
ghcr.io/sbomify/sbomify-actionMounting the Docker socket gives the container control of the host daemon. Prefer a rootless or remote daemon where your platform supports it. Pulling from a registry rather than a local daemon avoids the socket entirely.
VCS information
Repository details are auto-detected on every runtime - from the vendor’s own variables where it publishes them, and from the git checkout you mounted everywhere else (see VCS detection below). Set them in sbomify.json when you want something else recorded, or when there is no checkout to read:
{
"vcs_url": "https://git.example.com/my-org/my-repo",
"vcs_commit_sha": "abc123def456",
"vcs_ref": "main",
"supplier": {"name": "My Company"},
"lifecycle_phase": "build"
}Generate it from whatever variables your platform provides, then set AUGMENT=true. See augmentation.
Platform examples
The syntax differs; the substance does not.
Drone CI and Woodpecker
steps:
- name: generate-sbom
image: ghcr.io/sbomify/sbomify-action
commands:
- sbomify-action
environment:
LOCK_FILE: requirements.txt
OUTPUT_FILE: sbom.cdx.json
ENRICH: "true"
UPLOAD: "false"Buildkite
steps:
- label: "Generate SBOM"
plugins:
- docker#v5.11.0:
image: "ghcr.io/sbomify/sbomify-action"
workdir: /workspace
environment:
- LOCK_FILE=requirements.txt
- OUTPUT_FILE=sbom.cdx.json
- ENRICH=true
- UPLOAD=falseConcourse
jobs:
- name: generate-sbom
plan:
- get: repo
- task: sbom
config:
platform: linux
image_resource:
type: registry-image
source: { repository: ghcr.io/sbomify/sbomify-action }
inputs:
- name: repo
outputs:
- name: sbom
params:
LOCK_FILE: repo/requirements.txt
OUTPUT_FILE: sbom/sbom.cdx.json
ENRICH: "true"
UPLOAD: "false"
run:
path: sbomify-actionTeamCity has its own page.
A plain shell script - the docker run invocation at the top of this page works in cron, a Makefile, or a deployment script.
VCS detection
Repository URL, commit SHA and branch are detected from the git checkout you mounted, so a plain docker run on any CI system records provenance without configuration. It needs the .git directory inside the mount - -v "$(pwd):/workspace" from a repository root gives you that - and a remote on the repository.
One thing to know: the action treats a run as CI when the environment sets CI=true or a vendor variable it recognises (CIRCLECI, TRAVIS, TF_BUILD, BUILDKITE, DRONE, APPVEYOR, CODEBUILD_BUILD_ID, or one of the Jenkins markers JENKINS_URL, JENKINS_HOME, HUDSON_HOME, JENKINS_NODE_COOKIE, JENKINS_SERVER_COOKIE). Nearly every CI system sets one of those. If yours does not - a cron job or a shell script on a build box, say - pass -e CI=true, or -e SBOMIFY_LOCAL_VCS=true to get the same detection without claiming to be CI. Otherwise the run counts as local, where reading the checkout is opt-in.
A docker run passes nothing from the surrounding job into the container unless you ask it to, which matters on platforms that have a dedicated integration of their own: forward the vendor’s variables with the bare -e NAME form and the action reads them, rather than falling back to the checkout. See Jenkins, CircleCI and Travis CI.
sbomify.json overrides whatever is detected. See augmentation.
What is in the image
The image is deliberately small: Python, the sbomify CLI (which brings cyclonedx-py with it), conan for C and C++ metadata, and git.
Everything else - Syft, cdxgen, the JVM toolchain, Go, Rust, PHP, .NET, crane and cosign - is downloaded on first use, verified against a digest pinned at build time, and cached. You do not install anything; the tool fetches exactly the versions it was tested against.
Two practical consequences:
- Cache the runtimes or every run re-downloads them. Point
SBOMIFY_TOOL_CACHEat a volume, as in the caching example above. See tool runtimes. - Air-gapped runners need
SBOMIFY_FETCH_RUNTIMES=0, plus whatever generators you need preinstalled. Without them the run falls back to a lesser generator rather than failing, so check the output.
The container runs as root, because it needs to write to the mounted workspace. Files it creates will be root-owned on the host unless you pass --user.
Next steps
- Configuration reference - every option
- Augmentation - your business metadata, and overriding detected VCS details
- Advanced - caching, audit trail, troubleshooting