If your application does not need the flagged package at runtime, remove it from the final image rather than patching it. A package that is not there cannot be exploited and will not show up in the next scan. Patch just what the image must keep.
Why removal wins
Docker's build guidance says to avoid installing extra packages just because they might be nice to have, and notes the benefits: less complexity, fewer dependencies, smaller files and shorter builds. Each package is a future row in a scan, and a rebuild obligation. The cheapest fix is to never ship it.
Build tools are the usual culprit: compilers, headers, package managers, curl, git. They are needed to build, not to run.
Decide in four questions
- Does the running service load or call it? Check imports, linked libraries and entry points.
- Does anything in the image start it, such as a health check script or entrypoint?
- Does an operator need it for debugging in production? If so, consider an ephemeral debug container instead of baking tools in.
- Is it a dependency of something that is needed? Removal may break the package manager's dependency tree.
| Answer pattern | Decision |
|---|---|
| Not used, not depended on | Remove from the final stage |
| Used at build time | Move to a build stage |
| Used at run time | Keep, patch or upgrade |
| Needed just for debugging | Use an ephemeral container |
How to remove it properly
Deleting a file in a later Dockerfile step does not shrink the image or remove the finding, because the earlier layer still holds it. The OCI layer specification describes a layer as a changeset applied on top of earlier ones, with removals recorded as markers in the later layer. A scanner reading each layer can still see the package in the earlier one. Two reliable approaches:
- Install and clean up in the same step, so the files never land in a layer.
- Use a multi-stage build and copy just the artifacts you need into a small final stage.
FROM build-base AS build
RUN make install DESTDIR=/out
FROM minimal-runtime
COPY --from=build /out /
For more, see multi-stage builds shrink attack surface and a smaller image does not always mean fewer vulnerabilities.
When you cannot remove it
If the package is needed, patch by rebuilding on a fresh base with the pull flag. If no fix exists, see no fix available.
Record the decision
Write down which packages you removed and why. When a scanner flags a package, a record that says "removed in build 412, not present in digest sha256:..." closes the finding cleanly. Reachability analysis from tools like Safeguard's can support the decision by showing that the flagged component is not called by your code. Free scan: gold.safeguard.sh/scan.
A worked example
Suppose a scan flags a compression library, and your service is a compiled web server that never calls it. You look at the final image and find the library arrived as a dependency of a download tool that a Dockerfile step used to fetch a release archive. The download tool and the library are build-time needs.
The fix is to do the download in a build stage and copy the extracted files into the final stage. The final image no longer contains the tool or the library. The next scan has no row for either. Nothing was patched, and nothing needs to be patched next quarter either.
Compare that with patching: you would have waited for a fixed version, rebuilt, and then repeated the work for every future advisory against a package you never used.
Quick check
Will deleting the package in the last Dockerfile line clear the finding?
No. Earlier layers still contain it. Remove it before it enters a layer, or use a separate final stage.
Is removal safe if my tests pass?
Tests help, but also check entrypoints, health checks and any shell scripts that call the tool.
Sources
Last reviewed: 19 October 2026.