CRA Desk
Every generate step

Generating an SBOM for Container image (Alpine / apk)

The step below writes a CycloneDX JSON software bill of materials (SBOM) in your own pipeline. The CRA Desk scan reads that file and turns it into a Cyber Resilience Act (CRA) readiness report - it never generates one for you.

Run in CI:

docker build -t app:ci .
curl -sSfL https://get.anchore.io/syft | sh -s -- -b /usr/local/bin
syft app:ci -o cyclonedx-json=sbom.json
Produces:
sbom.json
Format:
CycloneDX JSON
Generator documentation

What the generator actually reads

syft reads the apk database inside the image, so the inventory is what the image actually ships after every apk add and apk del in the Dockerfile - including packages pulled in as dependencies of something you asked for, and excluding anything a later layer of the same stage removed. It describes the artefact rather than the repository, which is why this is the one recipe whose SBOM cannot exist before the build.

Versions and package URLs

Components arrive as pkg:apk/alpine/musl@1.2.5-r11 carrying ?distro= and, where a binary package was split out of a larger source package, ?upstream=. Both qualifiers do work: the -rN suffix is an Alpine package revision ordered by apk's own rules rather than SemVer, and the upstream name is how OSV keys the advisory - an advisory against openssl is what makes libcrypto3 in your image a finding.

The whole CI job

GitHub Actions

- uses: actions/checkout@v4
- run: docker build -t app:ci .
- run: curl -sSfL https://get.anchore.io/syft | sh -s -- -b /usr/local/bin
- run: syft app:ci -o cyclonedx-json=sbom.json
- uses: mmalinowski/cradesk-action@v1
  with:
    sbom-path: sbom.json

GitLab CI/CD

generate-sbom:
  script:
    - docker build -t app:ci .
    - curl -sSfL https://get.anchore.io/syft | sh -s -- -b /usr/local/bin
    - syft app:ci -o cyclonedx-json=sbom.json

include:
  - component: gitlab.com/cradesk/scan/readiness@1
    inputs:
      sbom_path: sbom.json

Only the first step changes with your stack - the CRA Desk step after it is identical everywhere.

What goes wrong here

syft scans the built image, not the checkout - pointed at the repository it produces an SBOM with no apk packages in it at all. Scan the same tag you ship: in a multi-stage build only the final stage's packages are in the inventory, which is the point of scanning the image rather than the build.

Repositories with more than one thing in them

One image is one SBOM, so a repository building several needs a generate and scan pass per image, and each report then describes that image rather than the repository. Pinning the base image by digest is what makes the answer reproducible: FROM alpine:3.21 quietly moves to a new patch release, and with it the package versions the last report was written against.

What CRA Desk does with the result

Components from this build carry pkg:apk package URLs, which the watch matches against OSV's Alpine advisories when the SBOM arrives and again as new advisories land. Matching runs on every plan; e-mail alerts and the full match list are what a paid plan adds.

A generator that runs is not the same as coverage. Components with no purl, no version, or from an ecosystem outside the ones we watch are counted as gaps in every report rather than reported as clean.