TRENDING
A mountain gorilla looks at the camera from green forest undergrowth
October 29, 2026
How to Compress Time-Series Metrics in Python With Gorilla Delta-of-Delta and XOR Encoding
A bronze-framed hourglass with white sand beside a small Ferris wheel ornament on a wooden table against a teal wall
October 29, 2026
NIST’s Final Post-Quantum Transition Report Puts Every Quantum-Vulnerable Public-Key Scheme on One 2030 and 2035 Clock, and Three Federal Documents Word the Deadline Three Ways
Six weathered stone columns of the Temple of Apollo at Delphi, Greece, standing on a stone platform above a valley of olive groves
October 29, 2026
Honeypots Recorded Probes Against Oracle E-Business Suite CVE-2026-101767 Seven Days After Its Patch, and the Finder’s Write-Up Is Dated Patch Day
Vintage silver stopwatch with a chain on a black background, its dial showing seconds from 5 to 60
October 29, 2026
How to Find a Slow Query in PostgreSQL 18 With pg_stat_statements, Read Its EXPLAIN ANALYZE Plan, and Fix It With the Right Index
A wooden Tower of Hanoi puzzle on a market table, with three pegs and stacked discs of different sizes
October 29, 2026
Two Labs’ Claimed Refutations of the Exponential Time Hypothesis Turn Verification Into a Statement-Reading Problem, and Their Lean Theorems Promise Only That a Constant Exists
29 Oct 2026
SXZ.io SXZ.io
  • Home
Search the Site
Popular Searches:
Technology Amazon AI
Recent Posts
A flock of snow geese in flight, seen as dark silhouettes against an orange and red sunset sky with layered clouds
How to Run Zero-Downtime Schema Migrations on PostgreSQL 18 and Block Unsafe ALTER TABLE Statements With a Python Lint Gate
October 28, 2026
An old green steel truss bridge resting on temporary supports beside a new arch bridge under completion on a river in Portland, Oregon
The Angular 2 Rewrite Turns a Migration Lesson Into a Security Bill: AngularJS Still Draws 533,480 Downloads a Week, and 10 Advisories Have No Open-Source Fix
October 28, 2026
Hundreds of yellow rubber ducks floating in a river enclosure during a duck race
WordPress 7.1.4 Closes an Unauthenticated Race in Its Own Update Routine, but 19 Older Branches Had No Backport Hours After Release
October 28, 2026
SXZ.io SXZ.io
  • Home

Categories

Articles 270 Posts
News 273 Posts
Learning Hub 243 Posts
Home/Learning Hub/How to Generate and Sign an SBOM for a Container Image With Syft and Cosign
Learning Hub

How to Generate and Sign an SBOM for a Container Image With Syft and Cosign

A beginner-friendly, step-by-step tutorial that walks you through generating a Software Bill of Materials with Syft and cryptographically signing it with Cosign and Sigstore, so anyone downstream can...

July 6, 2026 11 Min Read
145

In July 2026, Canonical published survey research based on 500 DevOps professionals showing that 48% believe verifiable, tracked packages would improve their confidence in software supply chain security, while 35% of organizations still rely on manual code review for security and 71% report real tension between DevOps and platform teams over how open source is managed at scale. The gap between “we should know what is in our software” and actually proving it is a tooling problem, and it already has a free, open-source answer: generate a Software Bill of Materials (SBOM) for every container image you ship, then cryptographically sign it so anyone downstream can verify exactly what is inside and who vouches for it.

Table Of Content

  • What you will learn: SBOMs, signing, and chain of trust, in plain terms
  • Prerequisites
  • Step 1: Push a test image to a registry you control
  • Step 2: Install Syft and list what is inside your image
  • Step 3: Generate SPDX and CycloneDX SBOM files
  • Step 4: Install Cosign and create a signing key pair
  • Step 5: Sign the image by digest
  • Step 6: Attach the SBOM as a signed attestation
  • Step 7: Verify the signature and the attestation
  • Common mistakes and how to catch them
  • Verifying it works end-to-end
  • Next steps
  • Sources

By the end of this walkthrough, you will have pushed a container image to a registry, generated a machine-readable SBOM for it with Anchore’s Syft, signed the image and attached the SBOM as a verified attestation using Sigstore’s Cosign, and verified the entire chain end-to-end from a clean terminal. Everything here uses free, open-source command-line tools and is cross-checked against Anchore’s official Syft documentation, the Sigstore Cosign documentation, and Canonical’s chain of trust research.

What you will learn: SBOMs, signing, and chain of trust, in plain terms

A Software Bill of Materials (SBOM) is, in Anchore’s words, a detailed list of all the libraries and components that make up a piece of software. If your container image includes fifty operating system packages and a dozen application dependencies, an SBOM is the itemized receipt naming every one of them, by name and exact version. Without one, answering “are we affected by this newly disclosed vulnerability?” means guessing; with one, it means running a query.

An SBOM alone only tells you what a scan found. It does not tell you whether that list is genuine or whether the image has been swapped for something else since the SBOM was generated. That is what signing solves. Sigstore is an open-source project for signing, verifying, and recording the provenance of software artifacts, and Cosign is its command-line tool for signing and verifying container images. When you sign an image, you create a cryptographic proof, tied to a specific key, that a specific identity vouches for a specific, unchangeable copy of that image.

Combine the two and you get an attestation: a signed statement that binds a claim (in our case, “here is the SBOM for this exact image”) to that image’s cryptographic identity. This is the mechanism behind the phrase “chain of trust”, not one certificate, but a series of verifiable links: this exact image maps to this exact package list, and this exact signer vouches for that mapping. That is precisely the gap Canonical’s research identifies: most organizations have vulnerability scanners, but far fewer have a verifiable, machine-checkable record of what shipped and who approved it.

Prerequisites

  • Operating system: Ubuntu 24.04 LTS or 26.04 LTS is used for the commands below, but every tool here is a portable binary, so the same steps work on any Linux distribution, and Cosign and Syft both also ship macOS and Windows builds.
  • Docker: installed and running, with a working docker command. Confirm with docker --version.
  • A free Docker Hub account: sign up at hub.docker.com if you do not already have one. Signing and attesting attach data to an image reference that lives in a real registry, so you need somewhere you can push to.
  • jq: a command-line JSON processor used to inspect SBOM output. Install it with sudo apt install jq on Ubuntu/Debian.
  • Prior knowledge assumed: comfort running commands in a terminal and basic familiarity with Docker (pulling and pushing images). No prior experience with SBOMs, Sigstore, Cosign, or Syft is assumed: each concept is explained the first time it appears.

Step 1: Push a test image to a registry you control

Cosign signs and attests a specific image reference stored in a registry, so the very first thing you need is an image you can push to. We will use the small, public alpine image as our test subject, re-tagged into a repository under your own Docker Hub username.

# Context: Ubuntu 24.04 LTS or newer, Docker installed and running.
# Purpose: pull a small public image and push a copy into a registry path you control.
docker pull alpine:latest
docker login
docker tag alpine:latest docker.io/<dockerhub-username>/sbom-demo:latest
docker push docker.io/<dockerhub-username>/sbom-demo:latest

Expected output: docker login asks for your Docker Hub username and password (or access token) and prints Login Succeeded. docker push uploads each image layer and finishes with a line similar to:

latest: digest: sha256:0a1b2c3d4e5f... size: 1234

That sha256:... value is the image’s digest, a cryptographic hash of its exact contents. You can fetch it again at any time with:

# Context: same machine, image already pushed.
# Purpose: look up the exact digest of the image you just pushed.
docker inspect --format='{{index .RepoDigests 0}}' docker.io/<dockerhub-username>/sbom-demo:latest

Expected output: docker.io/<dockerhub-username>/sbom-demo@sha256:0a1b2c3d4e5f...

Why the digest matters: a tag like :latest is a mutable pointer that can be repointed at completely different content tomorrow. A digest identifies the content itself and cannot be reused for anything else. Every signing, attesting, and verifying command in the rest of this tutorial targets the image by its @sha256:... digest, never by tag: keep the digest string handy, since you will reuse it in every step from here on.

Step 2: Install Syft and list what is inside your image

Syft is Anchore’s open-source CLI tool for generating an SBOM from container images and filesystems. Install the latest release with the official installer script:

# Context: Ubuntu 24.04 LTS or newer, sudo access.
# Purpose: download and install the latest Syft release to /usr/local/bin.
curl -sSfL https://get.anchore.io/syft | sudo sh -s -- -b /usr/local/bin
syft version

Expected output: the install script prints where it placed the binary, and syft version prints a version string confirming Syft is on your PATH.

Now point Syft at the image you pushed, using its digest:

# Context: image already pushed to Docker Hub in Step 1.
# Purpose: list every package Syft finds inside the image.
syft docker.io/<dockerhub-username>/sbom-demo@sha256:<your-digest>

Expected output: a human-readable table with NAME, VERSION, and TYPE columns, one row per package Syft found: for a plain Alpine base image this typically includes entries like alpine-baselayout, busybox, and apk-tools, each tagged as an apk package.

What just happened? Syft pulled the image’s layers, ran its built-in catalogers against them (Alpine’s apk database, Debian’s dpkg, language package managers, and dozens more depending on what it finds), and printed every installed package it could positively identify. By default it only reports what is visible in the final, squashed image; add --scope all-layers to also see packages that were installed and later removed in an earlier layer, often a useful thing to audit in its own right.

Step 3: Generate SPDX and CycloneDX SBOM files

A table on your screen is not something a scanner or a signing tool can consume. Ask Syft for machine-readable output in the two dominant industry-standard SBOM formats: SPDX (maintained under the Linux Foundation) and CycloneDX (maintained by OWASP).

# Context: same image reference as Step 2.
# Purpose: write the SBOM to disk in both SPDX and CycloneDX JSON formats.
syft docker.io/<dockerhub-username>/sbom-demo@sha256:<your-digest> \
  -o table \
  -o spdx-json=sbom.spdx.json \
  -o cyclonedx-json=sbom.cdx.json

Expected output: the same package table prints to your terminal, and two new files appear in your working directory: sbom.spdx.json and sbom.cdx.json.

Confirm the files actually contain what you expect using jq:

# Context: sbom.spdx.json and sbom.cdx.json created above.
# Purpose: list every package name each SBOM file records.
jq '.packages[].name' sbom.spdx.json
jq '.components[].name' sbom.cdx.json

Expected output: a quoted, newline-separated list of package names, identical in substance between the two files: they describe the same image, just structured differently (SPDX nests data under a top-level packages array, CycloneDX under components).

We will carry sbom.spdx.json forward into the signing steps, because Cosign has a built-in predicate type that maps directly onto SPDX JSON, which keeps the next commands simple.

Step 4: Install Cosign and create a signing key pair

Cosign is Sigstore’s command-line tool for signing and verifying container images and other artifacts. Install the latest release binary:

# Context: Ubuntu 24.04 LTS or newer, sudo access.
# Purpose: download and install the latest Cosign release to /usr/local/bin.
curl -O -L "https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64"
sudo mv cosign-linux-amd64 /usr/local/bin/cosign
sudo chmod +x /usr/local/bin/cosign
cosign version

Expected output: cosign version prints version and build information, confirming the binary is installed and executable.

Cosign supports two ways of signing: keyless signing, where you authenticate with an OpenID Connect identity (Google, GitHub, or Microsoft) and Cosign requests a short-lived certificate from Sigstore’s public certificate authority, Fulcio, for each signature; and key-based signing, where you generate your own long-lived key pair and use it directly. Keyless signing is the better fit for CI pipelines, since there is no private key to store as a secret, but it requires an interactive OIDC login, which adds a moving part to a first walkthrough. This tutorial uses key-based signing so every command is fully reproducible offline; the “Next steps” section below links to keyless signing once you are comfortable with the basics.

# Context: same machine as above.
# Purpose: generate a Cosign key pair for signing.
cosign generate-key-pair

Expected output: Cosign prompts you to enter (and confirm) a password, then writes two files to the current directory: cosign.key (an encrypted private key) and cosign.pub (the corresponding public key).

This is the single most important file in this tutorial to handle carefully: cosign.key plus its password together let anyone sign artifacts as you. Treat it exactly like an SSH private key: never commit it to version control, and store a backup somewhere access-controlled. cosign.pub, by contrast, is meant to be shared; anyone who wants to verify your signatures needs it.

Step 5: Sign the image by digest

# Context: cosign.key created in Step 4, image pushed in Step 1.
# Purpose: cryptographically sign the image, identified by its digest.
cosign sign --key cosign.key --yes docker.io/<dockerhub-username>/sbom-demo@sha256:<your-digest>

Expected output: a prompt for the cosign.key password, followed by a line confirming the signature was pushed to the registry, similar to Pushing signature to: docker.io/<dockerhub-username>/sbom-demo:sha256-<digest>.sig.

The --yes flag skips an interactive confirmation prompt that Cosign shows by default, warning you that the signature (and, by default, the fact that you made it) will be recorded in Sigstore’s public Rekor transparency log, a tamper-evident, publicly auditable ledger. That is worth pausing on: even with your own private key, Cosign timestamps the signing event publicly by default, so anyone can later confirm a signature existed at a given time, independent of trusting your key alone. Omit --yes the first time you run this if you want to see that prompt for yourself.

Cosign’s own documentation is explicit about why we have been signing by digest rather than by tag this whole time: “Make sure to sign the image by its digest (@sha256:…) rather than by tag (:latest) so that you actually sign what you think you’re signing! This prevents race conditions or (worse) malicious tampering.” A tag can be silently repointed between the moment you decide to sign and the moment the signature is created; a digest cannot.

Step 6: Attach the SBOM as a signed attestation

A signature on the image proves the image itself is untampered and identifies who signed it. An attestation goes further: it binds a specific, structured claim (your SBOM) to that image, and signs the whole package together, using the in-toto attestation format. Downstream, anyone can ask “what packages does this image contain, and is that claim itself signed?” instead of trusting an unsigned file sitting next to the image.

# Context: sbom.spdx.json from Step 3, cosign.key from Step 4.
# Purpose: attach the SBOM to the image as a signed, verifiable attestation.
cosign attest --predicate sbom.spdx.json --type spdxjson --key cosign.key --yes \
  docker.io/<dockerhub-username>/sbom-demo@sha256:<your-digest>

Expected output: another password prompt, then confirmation that the attestation was pushed to the registry alongside the image and its signature.

Watch the spelling here: Syft’s output flag is spdx-json (with a hyphen), but Cosign’s predicate type flag is --type spdxjson (no hyphen). They are two different tools’ naming conventions for the same standard, and mixing them up is an easy, silent mistake: Cosign accepts slsaprovenance, link, spdx, spdxjson, cyclonedx, vuln, openvex, or custom as its built-in --type values, per the official cosign attest reference.

Step 7: Verify the signature and the attestation

This is the step a downstream consumer, a CI gate, or a future version of you actually runs before trusting the image:

# Context: cosign.pub from Step 4, same image digest throughout.
# Purpose: verify the image signature and the SBOM attestation independently.
cosign verify --key cosign.pub docker.io/<dockerhub-username>/sbom-demo@sha256:<your-digest>
cosign verify-attestation --key cosign.pub docker.io/<dockerhub-username>/sbom-demo@sha256:<your-digest>

Expected output: cosign verify prints a JSON array describing the checks it performed (confirming the signature matches the public key and the image digest) followed by the signature payload. cosign verify-attestation does the same for the attestation, and its payload contains your signed SBOM predicate. Both commands exit with status code 0 on success: check with echo $? immediately after running one, if you want to confirm this in a script rather than by reading output.

Common mistakes and how to catch them

  • Signing or verifying by tag instead of digest. If you run any of these commands against :latest instead of @sha256:..., you are trusting whatever the tag happens to point to right now, which defeats the purpose. Always resolve and reuse the digest from Step 1.
  • Losing the cosign.key password. There is no recovery mechanism: a forgotten password means the key is useless and you must generate a new pair and re-sign everything with it. Store the password in a password manager the moment you create it.
  • Confusing spdx-json (Syft’s output flag) with spdxjson (Cosign’s predicate type). Both are valid, unrelated flags in two different tools; typing the wrong one produces a confusing error rather than a helpful one.
  • Trying to sign an image that was never pushed. Cosign needs to write the signature and attestation into a real registry alongside the image; a purely local docker build output with no corresponding registry copy has nothing for Cosign to attach to.
  • Committing cosign.key to a Git repository. Add it to .gitignore immediately after generating it, exactly as you would an SSH private key or a .env file full of secrets.
  • Being surprised by the Rekor confirmation prompt. If you forget --yes, cosign sign and cosign attest will pause and ask you to confirm that the event will be recorded in the public transparency log before continuing: this is expected behavior, not a hang.

Verifying it works end-to-end

Before you rely on this in a real pipeline, work through this checklist:

  1. Run syft <image>@<digest> -o table again and confirm the package list still matches what you expect for the base image you chose.
  2. Run cosign verify --key cosign.pub <image>@<digest> and confirm it exits with status 0 and prints the expected signature checks.
  3. Run cosign verify-attestation --key cosign.pub <image>@<digest> and confirm it exits 0 and the printed payload names spdxjson as its predicate type, that is Cosign confirming the attached, signed claim is the SBOM you attested in Step 6, not just any signed statement.
  4. As a negative test, run cosign verify --key cosign.pub against a different image reference, or generate a second, throwaway key pair and try verifying with the wrong public key. Confirm Cosign refuses and exits non-zero, proving the check actually discriminates between valid and invalid signatures, rather than passing everything.

Next steps

Once this manual walkthrough makes sense, the natural next moves are: switching from key-based to keyless signing in CI, using the OIDC identity GitHub Actions or GitLab already provides so there is no private key to manage as a secret; enforcing verification automatically at deploy time in Kubernetes with Sigstore’s policy-controller or a Kyverno policy, so unsigned or unattested images are rejected by the cluster rather than by a human remembering to check; and generating SLSA provenance attestations (cosign attest --type slsaprovenance1) alongside your SBOM, which additionally records how and where the image was built. Each of these is exactly the kind of “verifiable provenance” investment Canonical’s July 2026 research found DevOps teams increasingly want, but have not yet automated.

Sources

  • Canonical / Ubuntu Blog: Building an Open Source Chain of Trust: New Research Uncovers Key Blockers and Ways Forward
  • Anchore Open Source: Syft Getting Started Guide
  • Anchore Syft: GitHub Repository and README
  • Sigstore Documentation: Cosign Installation
  • Sigstore Documentation: Signing Containers
  • Sigstore Documentation: Verifying Signatures
  • Sigstore Cosign: cosign attest CLI Reference
  • Featured image source: Metal chain, Wikimedia Commons

Image credit: JJ Ying, CC0 1.0 Universal (Public Domain Dedication) via Wikimedia Commons; cropped, resized, and converted to WebP for sxz.io.

Tags:

CosignSBOMSigstoreSoftware Supply ChainSyft

Share

Coiled yellow fiber-optic patch cable with connectors at both ends on an optics lab bench
Previous Post

Red Hat’s BackendTLSPolicy Turns Gateway API Migration Into a TLS Parity Test

Padlock and chain securing a turquoise chain-link gate, representing an authentication bypass in self-hosted Git infrastructure
Next Post

Gitea’s Critical Docker Auth Bypass Is Now Under Active Exploitation

No Comment! Be the first one.

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Latest
28 Oct
How to Run Zero-Downtime Schema Migrations on PostgreSQL 18 and Block Unsafe ALTER TABLE Statements With a Python Lint Gate
28 Oct
The Angular 2 Rewrite Turns a Migration Lesson Into a Security Bill: AngularJS Still Draws 533,480 Downloads a Week, and 10 Advisories Have No Open-Source Fix
Trending
October 28, 2026
How to Run Zero-Downtime Schema Migrations on PostgreSQL 18 and Block Unsafe ALTER TABLE Statements With a Python Lint Gate
October 28, 2026
The Angular 2 Rewrite Turns a Migration Lesson Into a Security Bill: AngularJS Still Draws 533,480 Downloads a Week, and 10 Advisories Have No Open-Source Fix
October 28, 2026
WordPress 7.1.4 Closes an Unauthenticated Race in Its Own Update Routine, but 19 Older Branches Had No Backport Hours After Release
October 28, 2026
How to Compress Time-Series Metrics in Python With Gorilla Delta-of-Delta and XOR Encoding
October 28, 2026
NIST’s Final Post-Quantum Transition Report Puts Every Quantum-Vulnerable Public-Key Scheme on One 2030 and 2035 Clock, and Three Federal Documents Word the Deadline Three Ways
October 28, 2026
Honeypots Recorded Probes Against Oracle E-Business Suite CVE-2026-101767 Seven Days After Its Patch, and the Finder’s Write-Up Is Dated Patch Day

Related Posts

A laptop wrapped in a chain and padlock, illustrating least-privilege controls for AI agents.
Learning Hub

How to Secure Tool-Using AI Agents Before They Touch Production

June 8, 2026
Colorful sticky notes arranged on an office wall, symbolizing governance checklists and planning.
Learning Hub

AI Governance for Agentic Apps: A Practical Checklist for Builders

June 8, 2026
A technician connects green fiber optic cables at a data center, representing a private production inference endpoint.
Learning Hub

How to Deploy a Fine-Tuned LLM Behind a Private Production Inference Endpoint

June 8, 2026
Narrow aisle behind black supercomputer racks in a data center
Learning Hub

Kubernetes SELinux Volume Labeling: What Cluster Operators Should Audit Before v1.37

June 8, 2026
SXZ.io SXZ.io
  • [email protected]

Categories

Articles
Learning Hub
News

All Rights Reserved by SXZ.io ©2026