Image Vulnerability Scanning, Dependency Hygiene, Base-Image Maintenance, and Remediation Workflows: Concepts, Architecture, and Mental Model
Turn image scanning into a digest-bound remediation workflow by separating package inventory, advisory matching, exploitability context, declared-source updates, rebuilds, and verification.
Learning objectives
- Explain why a vulnerability report is meaningful only when bound to the exact image identity and platform that was scanned.
- Separate package inventory, advisory matching, severity, fix availability, exploitability context, rebuild state, and deployment decision.
- Record scanner version and vulnerability-database freshness alongside image digest evidence.
- Describe remediation as a declared-source change followed by a new build, new digest, and rescan—not a hot-fix inside a running container.
1. The practical problem: a red dashboard is not a remediation workflow
A scanner can produce hundreds of findings in seconds, but that output is not yet an operational decision. The first question is what exact bytes were scanned? A tag can move. A multi-platform tag can resolve to different manifests. A local build can be replaced under the same human-friendly name. If the image identity is unclear, the rest of the discussion can silently drift to a different artifact.
The second question is what does the scanner actually know? Scanners identify installed packages or language dependencies, compare them with advisory data, and report matches. They do not automatically prove that the vulnerable code path is reachable, that a published exploit applies to your runtime configuration, or that the application is otherwise secure. Conversely, a zero-finding report does not prove absence of unknown vulnerabilities, configuration flaws, leaked secrets, weak runtime permissions, or insecure application logic.
This chapter therefore treats scan output as one evidence source inside a controlled loop: identify → inventory → match → triage → change declared source → rebuild → rescan → decide.
2. Mental model: digest → inventory → advisory match → context → rebuild → verification
Start with an immutable image identity. For a registry artifact that is normally a manifest or index digest; for a local-only build, Docker also exposes an immutable image ID/config digest. The scanner expands that artifact into an inventory of OS packages and, where supported, language packages. It then joins that inventory to a vulnerability database assembled from advisory sources.
flowchart TD
A[Exact image identity + platform] --> B[Package / language inventory]
B --> C[Scanner vulnerability database]
C --> D[Advisory matches]
D --> E[Triage: fix available? reachable? exposed?]
E --> F[Declared base / package / source update]
F --> G[Rebuild -> new immutable identity]
G --> H[Rescan + compare]
H --> I[Promote, defer with owned exception, or iterate]
Each arrow changes ownership. Docker owns image identity and build state. The scanner owns inventory/matching behavior. Advisory publishers own vulnerability statements and fix metadata. Your team owns exploitability context, accepted-risk decisions, source updates, tests, and promotion.
3. State inventory before scanning
| State | What to record | Why it matters |
|---|---|---|
| Image identity | tag/reference, image ID or repository digest, platform | Prevents a report from being discussed against different bytes. |
| Scanner identity | tool name, exact version, invocation | Different releases can change analyzers, matching, and output schema. |
| Advisory data | database update/build timestamp or status | A scan is partly a statement about the database snapshot used. |
| Package evidence | package name, installed version, source/ecosystem | Lets you trace the finding back to the layer or dependency source you control. |
| Finding | CVE/advisory, severity source, fixed version, status | Separates fixability from severity. |
| Context | runtime exposure, reachable path, compensating controls | Supports triage without pretending severity alone is risk. |
| Remediation identity | changed source/base/dependency + new image ID/digest | Proves the fix was rebuilt into a different artifact. |
| Exception | owner, reason, expiry, exact digest/finding | Makes residual risk explicit and time-bounded. |
4. Read-only baseline: prove artifact and tool state first
Before changing a Dockerfile or pulling a different base image, capture the host/context and exact target identity. The commands below are read-only except that the scanner may refresh its local advisory database on first use.
docker version
docker context show
docker info --format '{{json .DriverStatus}}'
docker image inspect alpine:3.21.0 --format '{{.Id}} {{.Os}}/{{.Architecture}} {{json .RepoDigests}}'
trivy --version
If the image is not present, a pull is a state change and should be
recorded separately. After Trivy has initialized its database,
preserve trivy --version output because current
releases report scanner/database metadata useful for later
comparison.
5. Severity, fixability, exploitability, and exposure are different fields
Severity is usually inherited from an advisory source. Fixability asks whether the ecosystem reports a patched version. Exploitability asks whether the vulnerable behavior is realistically reachable under the application’s code paths and configuration. Exposure asks whether an attacker can reach the relevant surface. These are related, but they are not interchangeable.
A critical finding with a fixed version and internet-reachable code path may demand immediate rebuild. A high-severity library that is present but unreachable may still need a scheduled fix, but the operational response can differ. A medium finding with an active exploit and broad exposure can deserve more urgency than a higher numeric score with no applicable path. The record should preserve the original scanner output plus your contextual decision, not overwrite the source evidence.
6. Base-image maintenance is source maintenance, not container surgery
Images are immutable snapshots. If the vulnerable package came from the base image, update the declared base reference or digest and rebuild. If it came from an application lockfile, update that dependency in source and rebuild. If it came from a package installation step, update the declared package source/version and rebuild.
Changing packages interactively inside a running container creates
an unreviewed writable-layer mutation. It neither updates the
Dockerfile nor produces a release artifact with a new digest.
Docker’s current build guidance recommends rebuilding images often,
using --pull when intentionally tracking a tag, and
pinning by digest where reproducible supply-chain identity is
required.
7. DevOps evidence contract
A useful remediation packet answers: Which source revision produced the image? Which base/dependency inputs were used? What exact image identity/platform was scanned? Which scanner/database snapshot produced the finding? Which finding was selected, and why? What source change addressed it? What new image identity resulted? What did the rescan show? What residual findings remain, and who owns any exception?
8. Chapter path
Lesson 2 turns this model into a reproducible local workflow with Trivy. Lesson 3 compares scanner and policy choices. Lesson 4 deliberately breaks artifact identity and triage discipline. Lesson 5 produces a complete before/after remediation dossier that bridges naturally into Chapter 30’s SBOM, provenance, and signature evidence.
Knowledge check
Why is scanning myapp:release alone insufficient
evidence?
Because a tag is mutable. Record the immutable image identity/platform and scanner/database identity so later discussion cannot drift to different bytes.
Does a critical scanner severity prove the vulnerable path is exploitable in your deployment?
No. Severity is advisory metadata; exploitability and exposure require additional application/runtime context. Preserve both the advisory evidence and your contextual reasoning.
What is the correct remediation when a vulnerable package came from the base image?
Update the declared base/dependency source, rebuild a new image, record the new identity, test it, and rescan. Do not patch only the running container.
Why record the vulnerability-database timestamp?
Because advisory databases change over time. Two scans of identical bytes can differ if the scanner database or matching logic changed.
What must an accepted-risk record be bound to?
At minimum the exact image identity/platform and finding, plus owner, rationale, compensating controls where relevant, and an expiry/review date.
Official references and version notes
- Docker Engine 29 release notes — current Engine/CLI behavior; Engine 29.8.1 is the current release baseline used in this chapter.
-
Docker build best practices
— rebuild cadence,
--pull, digest pinning, and auditable base-image updates. - Docker Scout quickstart — package/vulnerability analysis and remediation workflow; used here as an optional Docker-native comparison path.
- docker scout cves reference — supported artifact types and local/registry resolution semantics.
- Trivy installation — current free local scanner installation; current documented release is 0.74.0.
- Trivy databases — vulnerability-database retrieval, update controls, and cache behavior.
- Trivy image reference — image scanning, JSON output, severity filters, and fixed/unfixed filtering.
- Grype vulnerability database — database freshness, update behavior, and age validation for the optional Grype path.
- Grype releases — current Grype release evidence; v0.119.0 was released 2026-09-17.
- Alpine release branches — support windows for the exact base-image family used in the disposable lab.
Docker Engine 29.8.1 is the current Engine baseline. Trivy 0.74.0 (released 2026-08-14) is the current released Trivy version used for the free/local mandatory path; Trivy 0.75.0 is not yet released as of this verification. Grype 0.119.0 (2026-09-17) is the optional comparison baseline. Docker Scout can analyze local artifacts and provide remediation guidance, while repository/cloud workflows can require Docker account/organization setup; it is optional here. Alpine 3.24 is the current stable branch, with 3.24.2 currently published; the lab uses exact human-readable tags and records the resulting immutable identities and scan database evidence at execution time.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.