Container Vulnerability Remediation: Base Image or Build?

Container findings come from two places, and each needs a different fix and a different owner.
Container vulnerability remediation: rank fixes by risk cleared, decide if each lands in the base image or your build, route it to the right owner.
November 11, 2026

A container scan hands you a list of vulnerable packages. It does not tell you where each one came from. Some arrived with the base image you named in your FROM line. Others were installed by your own RUN steps, or copied in with your application's dependencies. The fix for each is different, and so is the person who should make it.

Container vulnerability remediation stalls when that split is ignored. Application teams get tickets for packages they never installed. Platform teams get asked to patch libraries that only one service pulls in. The same OpenSSL advisory shows up in forty images and turns into forty tickets, when one base image bump would clear all of them.

This guide covers how to sort container findings by origin, rank the fixes by the risk they clear, decide where each fix lands and who owns it, and keep your registry scans focused on the images that matter.

Where container vulnerabilities come from

Every image is a stack of filesystem layers. The bottom layers belong to the base image: the distribution's packages, its C libraries, often a language runtime. The top layers belong to your build: packages you install, files you copy, the dependency tree your application resolves.

That makes the base image part of your software supply chain, chosen once and inherited by every build. A vulnerable package lives in exactly one of those places for a given image. That gives you three cases:

  • Base image. The package came with the base. You did not install it, and you probably cannot upgrade it cleanly from your own Dockerfile without fighting the base.
  • App build. Your build steps installed it, through a system package manager, a language package manager, or a vendored binary. You own the line that put it there.
  • Both. Across a fleet, the same package can come from the base image in some images and from build steps in others. One advisory, two different fixes.

Layer attribution is what makes this split possible. Each layer records the build instruction that created it, and a scanner that tracks which layers belong to the base can tell you which findings you inherited and which you added. Without it, every finding looks like your problem, and every fix looks like a package upgrade.

Container vulnerability remediation: rank by risk cleared

Severity counts are a poor queue. An image with two hundred medium findings in an unused base library is less urgent than an image with one known exploited vulnerability behind a public load balancer. Rank the fix, not the finding.

A fix is a package upgrade. One upgrade can clear many findings across many images, so the useful question is: which upgrades remove the most real risk? A practical order:

  1. Known exploited first. Upgrades that clear a vulnerability on the CISA Known Exploited Vulnerabilities (KEV) catalog go to the top. Exploitation in the wild is the strongest signal you have.
  2. Critical findings in running images. An image that no workload runs is inventory. An image that runs is attack surface.
  3. Critical findings in internet-facing images. Reachability from the internet raises the stakes for anything running.
  4. Everything else, by breadth. After that, prefer upgrades that clear the most findings across the most images. Exploit likelihood scores such as FIRST EPSS help break ties.

Two signals do most of the work here: whether an image is running, and whether what runs it is exposed. Both come from your runtime, not your registry. If your scanner only sees the registry, you are ranking blind. We cover how to weigh exploit evidence in more depth in Exploit Evidence: How to Read KEV, EPSS and Public PoCs.

Group by upgrade, not by advisory

The same advisory can affect several binary packages built from one source package. On Debian and Ubuntu, for example, one OpenSSL source package ships openssl, libssl3 and related libraries. Fixing one advisory means upgrading all of them together. Group findings by the upgrade that clears them, and the queue shrinks to the number of changes someone actually has to make.

Base image or build step: deciding where the fix lands

Once you know the origin, the fix follows. Here is a typical Python service Dockerfile:

FROM python:3.12.4-slim-bookworm

RUN apt-get update && apt-get install -y --no-install-recommends libxml2 curl && rm -rf /var/lib/apt/lists/*

COPY requirements.txt .

RUN pip install --no-cache-dir -r requirements.txt

COPY app/ /app

Suppose the scan reports three issues: a vulnerable libssl3, a vulnerable libxml2, and a vulnerable urllib3.

Fix 1: bump the base image

libssl3 was never installed by this Dockerfile. It came with the base. The fix is a newer base tag on the same release line:

FROM python:3.12.7-slim-bookworm

A patch bump like this keeps the same major Python version and the same Debian release, so the risk of breaking the application is low. It also clears every other vulnerability the new base no longer carries, not only the one you were chasing. Then rebuild and redeploy. A base bump that never ships clears nothing.

Docker's build best practices recommend pinning base images and rebuilding often for the same reason: the base is where most inherited fixes arrive. Check what the new tag introduces as well as what it removes. A newer base can carry advisories your current one does not. Compare two scan results, not two changelogs.

Fix 2: upgrade the package in the build step

libxml2 was installed by your own RUN line. A base bump may not help, because the version you get depends on the distribution's repositories at build time. Often a plain rebuild picks up the patched package. When you need a specific fixed version, pin it:

RUN apt-get update && apt-get install -y --no-install-recommends libxml2=<fixed-version> curl && rm -rf /var/lib/apt/lists/*

urllib3 is an application dependency. The fix belongs in requirements.txt, or the lockfile that produces it, not in the Dockerfile:

urllib3>=<fixed-version>

That change also fixes the same dependency anywhere else the service runs, such as local development or CI tests.

The tempting shortcut

You can add RUN apt-get update && apt-get upgrade -y to any Dockerfile and patch base packages in place. It works, and sometimes it is the right stopgap when the base publisher has not shipped a fixed tag yet. As a habit it has costs: the image drifts from the base it declares, builds stop being repeatable, and the next scan cannot cleanly say what the base contributed. Prefer moving the base. Patch over it only when you cannot.

Who owns each container fix

Origin decides ownership more than anything else.

  • Base image fixes belong to whoever owns the base. If teams pull public images directly, that is each image's owning team. If you publish internal golden images, it is the platform team that maintains them, and one bump plus a rebuild of downstream images clears the finding everywhere.
  • Build step fixes belong to the team that owns the repository with the Dockerfile. They wrote the RUN line or the dependency manifest, so they make the change.
  • Fixes that land in both places need two owners. Split the work explicitly so the platform team does not wait on application teams, or the reverse.

Ownership depends on knowing which repository built which image. That link is often weaker than people assume. Image labels, build provenance and deployment manifests all help, and each one you add makes routing more reliable. An image no team owns is a finding with nobody to fix it, so count unowned images as a gap to close, not a backlog to ignore.

Keep registry scans focused on the tags that matter

Registries fill up fast. Every branch build, pull request build and nightly tag is a new image. If your scanning picks images by recency alone, a burst of feature branch pushes can push your current release out of the scanned set. You end up with fresh results for images nobody will deploy and stale results for the one that runs.

A few habits keep scanning honest:

  • Scan what runs, always. Images referenced by live workloads should be scanned and rescanned regardless of tag. New advisories land against old images every day.
  • Use a tag convention. Release tags such as v2.4.1 and branch tags such as feat-login should be easy to tell apart with a pattern.
  • Filter by pattern, not by hand. Include release tags and exclude branch and pull request tags when choosing which registry images to scan. A pattern like ^v\d+\.\d+\.\d+$ keeps the selection on releases.
  • Let unused images age out. An image that has not been pushed or pulled in months is not attack surface. Keeping its findings in the queue only adds noise.

A container vulnerability remediation checklist

  1. Confirm your scanner records which layers came from the base image. Without layer attribution, you cannot route fixes.
  2. Connect runtime context so you know which images run and which are internet facing.
  3. Rank package upgrades, not individual findings: known exploited first, then critical findings in running or exposed images.
  4. For each upgrade, decide where it lands: base image, build step, or both.
  5. Route base fixes to the base owner and build fixes to the repository owner. Track unowned images as a gap.
  6. For base bumps, compare what the candidate removes, keeps and introduces before you merge.
  7. Rebuild and redeploy. Verify the next scan of the running image shows the finding gone.
  8. Keep registry scanning on release tags and running images, and filter out branch builds.

How Heeler handles container fixes

Heeler scans container images from your connected registries and the images your running workloads reference, and records each image's layers, including which came from the base image. The Containers area under Security turns that into a fix queue.

  • Remediations ranked by risk cleared. Browse by Remediations lists one package upgrade per row: upgrades that clear known exploited vulnerabilities first, then those that clear critical findings in running or internet-facing images. Each row shows the fixed version, the findings cleared, and how many affected images are running and exposed.
  • Where the fix lands. The Where column reads Base image, App build or Both. When a package comes from the base image, the drawer says to upgrade the base image rather than the package, and labels the package version as the fix to use if you cannot move the base.
  • Who owns it. The Owner column shows the team that owns the most affected images, with the other owning teams listed on hover, or Unowned.
  • Base image upgrades. Heeler looks for newer tags of an image's declared base that never cross a major version, scans up to three of them, and shows what each one removes, keeps and introduces. Where your role allows it, Open Dockerfile PR starts an agent execution that edits the base reference and opens a pull request.
  • Focused registry scans. Running images and pushed images are scanned whatever their tag, and images with a live workload are rescanned daily. For organizations where Heeler enables registry image selection, Scan Tag Filters on Docker Hub and AWS connections set include and exclude patterns, so branch and pull request builds do not displace the latest release.

Ownership comes from the link between an image and the repository that built it. Heeler matches the two by build provenance, image label, service correlation, deployment manifest or a manual link, and the Team filter selects images by the team that owns that source repository.

FAQ

Should I upgrade the base image or the package?

Upgrade the base image when the vulnerable package came from the base. Upgrade the package in your build step or dependency manifest when your own build installed it. Patching base packages in your Dockerfile is a stopgap for when no fixed base tag exists yet.

How do I prioritize container vulnerabilities?

Rank package upgrades by the risk they clear, starting with known exploited vulnerabilities. Then take critical findings in images that are running or internet facing, and after that the upgrades that clear the most findings across the most images.

Why does the same CVE appear in dozens of images?

Usually because the images share a base image or a common layer. One base image bump, followed by rebuilding the images that use it, clears the finding from all of them.

Does a newer base image tag always reduce vulnerabilities?

No, a newer tag can introduce advisories your current base does not have. Compare scan results for the candidate against your current image, and look at what it removes, what remains and what it introduces.

Which container images should I scan?

Scan every image that a live workload runs, and rescan it as new advisories publish. For registry images that are not running, scan release tags and filter out branch and pull request builds.

Heeler ranks container fixes by the risk they clear, shows whether each one belongs in the base image or the build, and routes it to the team that owns the image. To see it on your own images, Get a demo.

What’s new on Heeler
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Related resources

See All Resources