SBOM

Container SBOM Generation: Best Practices for 2025

Container images are multi-layered artifacts that challenge SBOM generators. Here is how to generate comprehensive, accurate SBOMs for containerized applications.

Bob
Solutions Engineer
6 min read

Generating an SBOM for a Node.js application is relatively straightforward -- parse package.json and package-lock.json, enumerate the dependency tree, done. Generating an SBOM for a container image that runs that Node.js application is a different challenge entirely.

A container image is a multi-layered artifact that combines an operating system, system libraries, runtime environments, application dependencies, and application code. Each layer comes from a different source with different provenance. Getting a complete, accurate SBOM for the whole thing requires understanding how container images are constructed and where SBOM generators fall short.

The Container SBOM Challenge

Consider a typical production container image:

FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-alpine
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
COPY package*.json ./
EXPOSE 3000
CMD ["node", "dist/index.js"]

This simple Dockerfile produces an image with at least four distinct categories of software:

  1. Alpine Linux packages -- musl libc, busybox, ca-certificates, etc.
  2. Node.js runtime -- the Node.js binary and its bundled dependencies
  3. npm packages -- your application's JavaScript dependencies
  4. Your application code -- the built artifacts in /app/dist

A complete SBOM needs to capture all four categories. Most SBOM generators excel at one or two and miss the rest.

Tool Landscape

The main tools for container SBOM generation in 2025:

Syft (by Anchore) -- the most comprehensive general-purpose container SBOM generator. Analyzes both OS packages and application dependencies across most package managers. Outputs CycloneDX and SPDX.

Trivy (by Aqua Security) -- primarily a vulnerability scanner that also generates SBOMs. Strong OS package detection. Good but not exhaustive application dependency support.

docker scout sbom -- Docker's built-in SBOM generation. Uses Syft under the hood. Convenient but limited to Docker Desktop contexts.

cdxgen -- OWASP's SBOM generator. Strong application dependency analysis, especially for Java and Node.js. OS package detection added more recently.

Tern -- specifically designed for container SBOM generation. Analyzes Dockerfiles and image layers to trace provenance. Good for understanding where each component came from.

No single tool produces a perfect container SBOM. The best results come from combining tools and merging their output.

Best Practice 1: Generate SBOMs at Multiple Stages

Do not rely on a single SBOM generated from the final image. Generate SBOMs at multiple points in your build pipeline:

Build-time SBOM -- generated from your application's dependency manifests before the container build. This captures your application dependencies with full resolution detail.

Image SBOM -- generated by scanning the built container image. This captures OS packages, runtime components, and the final state of application dependencies.

Merge and reconcile. The build-time SBOM typically has more detailed application dependency information (including devDependencies, build tools, and intermediate dependencies). The image SBOM captures what is actually in the deployed artifact. Merging both gives you the complete picture.

Best Practice 2: Account for Multi-Stage Builds

Multi-stage Dockerfiles are standard practice, and they create a gap that many SBOM tools miss. Build tools, compilers, and devDependencies that exist in the build stage are not present in the final image. This is correct from a deployment perspective -- those components are not deployed. But from a security and compliance perspective, you may need to document them.

Consider maintaining two SBOMs:

  • Deployment SBOM -- what is in the production image (the final stage)
  • Build SBOM -- what was used to build it (all stages combined)

The distinction matters for vulnerability triage. A vulnerability in a build-stage-only component cannot be exploited in production, but it could compromise your build pipeline.

Best Practice 3: Include Base Image Provenance

Your base image (e.g., node:20-alpine) is a supply chain dependency. Your SBOM should document:

  • The exact base image digest (not just the tag, which is mutable)
  • The base image's own SBOM (if available -- Docker Hub and other registries increasingly provide these)
  • The relationship between your image and the base image
{
  "type": "container",
  "name": "node",
  "version": "20-alpine",
  "hashes": [
    {
      "alg": "SHA-256",
      "content": "sha256:a1b2c3d4..."
    }
  ],
  "externalReferences": [
    {
      "type": "distribution",
      "url": "https://hub.docker.com/_/node"
    }
  ]
}

Pin your base image by digest in your Dockerfile for reproducibility:

FROM node:20-alpine@sha256:a1b2c3d4...

Best Practice 4: Capture OS-Level Dependencies

Alpine uses apk, Debian/Ubuntu use apt, Red Hat uses rpm. Container SBOM tools need to parse the package database for whatever OS your base image uses.

Common gaps:

  • Packages installed via apk add --no-cache that do not leave entries in the package database (this is actually a myth -- they do leave entries, but some cleanup scripts remove the cache, not the database)
  • Packages compiled from source during the build (these will not appear in any package database)
  • Shared libraries that are present on the filesystem but not tracked by the package manager

For the compiled-from-source case, your best option is to document these components manually in your SBOM or to use binary analysis tools that can identify software by its binary signatures.

Best Practice 5: Handle Language-Specific Quirks

Each language ecosystem has its own SBOM generation challenges in containers:

Node.js: node_modules may differ from what package-lock.json declares if npm ci was not used, or if post-install scripts modified modules. Scan both the manifest and the actual node_modules directory.

Python: Virtual environments, system-level pip installs, and conda environments can coexist in a container. Make sure your SBOM tool scans all of them. Also check for packages installed via pip install --user in the home directory.

Java: JAR files embedded within other JAR files (uber-JARs, shaded JARs) are notoriously difficult for SBOM tools. Use tools that understand Maven BOM files and can analyze nested JAR contents.

Go: Statically compiled binaries do not have external dependency files to parse. Use Go's built-in debug/buildinfo package or tools like go version -m to extract dependency information from compiled binaries.

Rust: Similar to Go -- statically compiled. Use cargo auditable to embed dependency information in the binary itself.

Best Practice 6: Automate and Integrate

Container SBOM generation should be:

  • Automated -- generated as part of your CI/CD pipeline, not manually
  • Versioned -- stored alongside the image it describes, tagged with the same version
  • Validated -- checked for schema compliance and minimum quality thresholds
  • Distributed -- published alongside your container image (OCI artifact references make this possible)

Store SBOMs as OCI artifacts attached to your container images:

# Generate SBOM
syft your-image:latest -o cyclonedx-json > sbom.json

# Attach to image in registry
cosign attach sbom --sbom sbom.json your-registry/your-image:latest

This makes the SBOM discoverable by anyone who pulls the image.

How Safeguard.sh Helps

Safeguard.sh generates and manages container SBOMs throughout their lifecycle. Our scanner handles multi-stage builds, OS packages, application dependencies, and base image provenance in a single pass. SBOMs are automatically versioned alongside your images, validated for quality, and monitored for new vulnerabilities. Our policy gates can enforce container-specific requirements -- minimum base image freshness, no known-vulnerable OS packages, mandatory SBOM attachment to registry images. Complete container SBOMs are the foundation for container security, and Safeguard makes sure yours are comprehensive and current.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.