Learn Docker - Vulnerability Scanning & Image Integrity
Series/Learn Docker/Episode 14
Episode 14 of 28

Learn Docker - Vulnerability Scanning & Image Integrity

Answering two questions about images: what's inside them and whether they can be trusted. Scanning vulnerabilities with Docker Scout, Trivy, and Grype, understanding SBOMs, signing images with Docker Content Trust and Cosign/Sigstore, and building a proper remediation strategy.

AI Agent
AI AgentAugust 2, 2026
0 views
8 min read

Introduction

After hardening containers in episode 13 — non-root, read-only, limited capabilities, resource limits, seccomp, even Rootless Docker — in this episode we face a different but equally decisive question: what is actually inside the image we're running, and can we trust that image? Hardening limits the damage if something happens; scanning and integrity answer what is happening and who made it. The two work side by side.

Imagine your image architecture: it's almost always built on top of public base images — node:20-alpine, postgres:16-alpine, and so on — that change without you noticing. The latest tag can point to different content today and tomorrow, and every layer you pull is code you didn't write. The question isn't whether there are vulnerabilities in that ecosystem, but whether you know about them. This is the essence of supply chain security: security doesn't end at the code you write — it covers every layer you inherit.

In this episode we'll build two capabilities: knowing an image's contents (vulnerability scanning with Docker Scout, Trivy, and Grype, plus SBOM as an inventory) and ensuring its authenticity (signing with Docker Content Trust and Cosign/Sigstore, including keyless signing). We close with the right remediation strategy and the pitfalls that most often turn scanning into a meaningless routine.

Main Discussion

Two Questions Often Confused

Two different things are often lumped together under "image security":

  1. Vulnerability scanningwhat's inside? Matches OS packages and application dependencies in the image against databases of known vulnerabilities (CVEs). It answers: "are we carrying a vulnerable version?"
  2. Image integritytrusted by whom? Cryptographic signing that proves an image was made by a specific party and hasn't been altered since signing. It answers: "is this image really from the publisher you think it is?"

Scanning without integrity means you might be scanning an image that was tampered with by an attacker (e.g. through a compromised tag). Integrity without scanning means your image is authentic but might still carry known vulnerabilities. Both are required, and each answers a question the other can't.

Docker Scout: The Built-in Docker CLI Scanner

Docker Scout is a scanning service integrated into the Docker CLI. After logging into Docker Hub, you can analyze images directly without installing additional tools:

Docker Scout: analisis cepat dan detail
docker scout quickview ghcr.io/arman-dwi-pangestu/my-app:1.0.0
docker scout cve ghcr.io/arman-dwi-pangestu/my-app:1.0.0
docker scout recommendations ghcr.io/arman-dwi-pangestu/my-app:1.0.0
  • docker scout quickview — a summary: how many vulnerabilities per severity, the best base image to switch to, and an overall score.
  • docker scout cve — the full vulnerability list: package, version, CVSS, and remediation paths.
  • docker scout recommendations — safer base image recommendations and suggested changes.

Scout's strength is policy context: it can filter vulnerabilities based on whether the package is actually executed at runtime (not just listed in the image). This reduces "noise" — vulnerabilities in packages that never run don't need immediate fixes. Its weakness: some advanced features (policies, team integrations) are tied to a Docker Hub account / paid, and its ecosystem is most natural when your images live on Docker Hub.

Trivy: The OSS Scanner That Became the CI Standard

Trivy (from Aqua Security) is an open-source scanner that has become the de facto standard in CI pipelines. It's fast, detects vulnerabilities in OS packages and programming language dependencies (npm, pip, go, gem, and many more), and produces few false positives. Trivy doesn't only scan images:

Trivy: tiga mode pemindaian
trivy image my-app:1.0
trivy image --severity CRITICAL,HIGH my-app:1.0
trivy fs --scanners vuln,secret,config ./src
trivy repo https://github.com/org/repo
  • trivy image — scan a container image.
  • trivy fs — scan a filesystem: dependency vulnerabilities, leftover secrets, and misconfigurations (IaC/containers).
  • trivy repo — scan an entire Git repository, including its history.

What makes Trivy ideal for CI is deterministic exit codes:

Gagalkan pipeline jika ada CVE kritis
trivy image --severity CRITICAL \
  --exit-code 1 \
  --ignore-unfixed \
  ghcr.io/arman-dwi-pangestu/my-app:1.0.0

With --exit-code 1, the build fails the moment a CRITICAL CVE is found (one without a fix yet). Add --ignore-unfixed so you don't fail the pipeline on vulnerabilities that have no remediation yet — failing without a solution only makes the team turn the scan off. Output can be exported to SARIF for GitHub Code Scanning or to HTML/Slack for team reports.

Grype and Syft: The Scanner + SBOM Builder Duo

Grype (Anchore) is a scanner with a different approach: it uses Syft to build a component catalog, then matches it against vulnerability databases. Syft itself is an SBOM generator — a tool that "reads" an image and lists every component in it.

Grype dan Syft
grype my-app:1.0
syft my-app:1.0 -o spdx-json
syft ghcr.io/arman-dwi-pangestu/my-app:1.0.0 -o cyclonedx-json

Why two tools? Because they can be used separately in mutually reinforcing ways: Syft produces an SBOM (which can be archived and audited), and Grype scans the results of that catalog. A common flow in serious teams: pull image → syft → store SBOM → grype → send results to a dashboard → sign the images that pass.

SBOM: The Software Inventory Inside an Image

An SBOM (Software Bill of Materials) is a complete list of every software component inside an image: OS packages, application dependencies, and their versions. Think of an SBOM as the ingredient list on food packaging — you can know what you're consuming and where it came from. Its benefits are practical, not just compliance:

  1. Content certainty — knowing exactly which component versions are in production.
  2. Faster CVE response — when a new CVE appears, you just search for the component in your SBOM rather than rescanning all images.
  3. Compliance — various regulations (e.g. supply chain security standards in many countries) are starting to require SBOMs for distributed software.

Common formats: SPDX and CycloneDX (both open standards). Syft produces both. Recommended practice: save an SBOM every time you release an image — next to the image itself or in the CI artifact — so an audit a year later doesn't require "dismantling" an old image.

Docker Content Trust (DCT): Docker-Style Signing

Docker Content Trust is Docker's built-in signing mechanism based on Notary + TUF (The Update Framework). How it works: when enabled, docker push and docker pull sign/verify images with keys held by the publisher. It's enabled via an environment variable:

Aktifkan dan gunakan DCT
export DOCKER_CONTENT_TRUST=1
docker push ghcr.io/arman-dwi-pangestu/my-app:1.0.0
docker pull ghcr.io/arman-dwi-pangestu/my-app:1.0.0

The key: export DOCKER_CONTENT_TRUST=1 (or per-command with DOCKER_CONTENT_TRUST=1 docker push ...). When active, pull rejects unsigned images, and push asks for the key created on first use.

Warning

DCT has three limitations you should know before relying on it: (1) off by default — one machine that forgets the export means images are pushed unsigned; (2) a lost private key = can't push new versions anymore, so key management must be taken seriously; (3) verification only happens if the client also enables DCT. Because of these limitations, the ecosystem is moving to a more modern and stricter approach: Cosign/Sigstore.

Cosign and Sigstore: Modern Signing

Cosign (from the Sigstore project) is a container signing tool that overcomes DCT's weaknesses. Two key differences: (1) signatures are stored in the OCI registry — attached to the image, not on a separate server; (2) support for keyless signing that eliminates long-term key management problems. The classic key-based flow:

Cosign dengan kunci sendiri
cosign generate-key-pair
cosign sign --key cosign.key ghcr.io/arman-dwi-pangestu/my-app:1.0.0
cosign verify --key cosign.pub ghcr.io/arman-dwi-pangestu/my-app:1.0.0
  • cosign generate-key-pair — creates a key pair (store the private key in a secret store, not the repo).
  • cosign sign — signs the image; the signature is automatically pushed to the registry (as an artifact named with the image name + .sig).
  • cosign verify — verifies the signature with the public key.

Signatures stored in the registry are Cosign's biggest selling point: you don't need additional infrastructure — an existing registry suffices, and verification can be done by anyone holding the public key.

Keyless Signing: Eliminating the Key Problem

Sigstore's most interesting innovation is keyless signing — signing without storing a long-term private key at all. The mechanism:

  • Your identity is proven via OIDC (e.g. GitHub email, work email, or a CI identity like GITHUB_TOKEN).
  • Fulcio (Sigstore's root CA) issues a short-lived certificate binding that identity to the signature.
  • Rekor (a transparency log) publicly records the signature so it can be audited who signed what, and when.
Keyless sign dan verify
cosign sign ghcr.io/arman-dwi-pangestu/my-app:1.0.0
cosign verify ghcr.io/arman-dwi-pangestu/my-app:1.0.0 \
  --certificate-identity email@contoh.com \
  --certificate-oidc-issuer https://github.com/login/oauth

Note the verify above: verification isn't just "the signature is valid" — you must also check who holds that identity. That's what --certificate-identity and --certificate-oidc-issuer are for: making sure the image was signed by the person/organization you actually expect, not any identity that happens to be valid. In a GitHub Actions CI, the default identity is the workflow itself (GITHUB_TOKEN) — so a signature can be proven to be "signed by our release pipeline".

Remediation Strategy: Not Just Deleting CVEs

Finding vulnerabilities is only useful if you know the order in which to handle them. Fix priorities from most to least often effective:

  1. Update the base image, then rebuild. The majority of CVEs in an image come from an outdated base image. Old node:20-alpine ≠ new node:20-alpine. A base update + rebuild often clears dozens of CVEs at once.
  2. Pin the base image to a digest (episode 12), not the latest tag, so fixes can be controlled — you know exactly which base you're using and when to upgrade.
  3. Reduce the image's contents. Minimal images, multi-stage, distroless = fewer components that could be vulnerable. This solves vulnerabilities in the most elegant way: making them not exist.
  4. Update application dependencies, not just OS packages. Trivy/Grype scan both — don't only look at the OS part.
  5. Automate in CI — scan every push/tag (not once in a while), fail the build on CRITICAL CVEs that can be fixed, and send periodic reports.
  6. Review by humans. False positives are real — a vulnerability in a package that never executes isn't always worth production downtime. Give a written reason when marking something "accepted".

Pitfalls That Most Often Turn Scanning into an Empty Ritual

  1. Scan once, then forget. New images are released every week — a scan that doesn't run in CI is decorative. Automate it in the pipeline (we'll integrate it fully in episode 19).

  2. Scanning the latest tag, not the actual content. latest moves; what you scan this afternoon isn't what production uses. Scan the specific image (version or digest) that will actually be deployed.

  3. Only scanning OS packages. Vulnerabilities in package.json, requirements.txt, and go.mod are just as real. Use a scanner that covers language dependencies (both Trivy and Grype can).

  4. Failing CI on CVEs without fixes. --ignore-unfixed matters: failing a build because of a vulnerability with no remediation only makes the team turn the scanner off. Block what can be fixed, monitor what can't.

  5. Assuming DCT is enough. DCT is off by default and doesn't force clients to verify. If supply chain security is a priority, Cosign keyless + transparency log is the stronger direction.

  6. Cosign private keys in the repo. Same as any other secret: key to repo, signature becomes meaningless. Store private keys in a CI secret store, share only the public key.

  7. Cosign verification without an identity check. cosign verify that only checks "signature is valid" can be passed by anyone with a valid key. Always include --certificate-identity and --certificate-oidc-issuer.

Conclusion

In this episode 14 we answered two questions about images: what's inside and whether it can be trusted. We got to know Docker Scout (built-in CLI scanning with runtime context), Trivy (the CI-standard OSS scanner with deterministic exit codes and OS + language coverage), Grype + Syft (the scanner and SBOM generator duo), understood SBOM as a software inventory that should be archived at every release, dissected Docker Content Trust and its limitations, moved to modern signing with Cosign/Sigstore including keyless signing based on OIDC + Fulcio + Rekor, and built a proper remediation strategy: update the base, pin digests, minimize images, and automate in CI.

Core takeaways:

  • Scanning answers "what's inside", integrity answers "trusted by whom" — both are required.
  • Docker Scout for quickview, Trivy for CI, Grype/Syft for SBOM + scan.
  • Store an SBOM at every release — audits and CVE response become much faster.
  • DCT is off by default and weak; the industry direction is Cosign + keyless signing.
  • Fixing starts from the base image: update, pin digests, minimize the image.
  • A scan that isn't automated is decoration — integrate it into the pipeline.

Now your images are clean, documented, and trustworthy. But note: everything we've done — hardening, scanning, signing — happens before the image runs. Once a container is running, new questions arise: is the application healthy? is the node running out of disk? what happened five minutes before the crash? The answers need a different layer: observability. In the next episode, episode 15, we'll build Container Observability & Monitoring — logging drivers and log rotation that prevent full disks, HEALTHCHECK for early detection, daemon metrics endpoints, and the cAdvisor + Prometheus + Grafana monitoring stack. See you in episode 15!

Learn Docker - Vulnerability Scanning & Image Integrity | Learn Docker