How to Patch Docker Base Image Vulnerabilities
Published July 11, 2026 · 9 min read
A scan report full of base-image CVEs is not a code problem — it is a build-process problem. This guide walks through patching Docker base image vulnerabilities in the right order: find what is actually vulnerable, pull the fix, apply OS-level updates, and lock in a cadence so the count does not creep back up.

Why base-image patches lag behind
A Docker image is not a live system — it is a frozen snapshot of packages at build time. Upstream maintainers publish patched base images continuously as advisories close, but your build only benefits from those patches the moment you rebuild. Between rebuilds, every fixed CVE upstream is still an open finding in your image.
That gap compounds. A base image pulled once at project start and never refreshed can silently accumulate a year of unpatched advisories, even though the maintainer fixed every one of them upstream. The six steps below close that gap and keep it closed.
What patching can and cannot reach — ScanRook v1.14.2, 2026-07-04
Step 1: Identify which base-image CVEs are actually present
Before patching anything, scan the current image and export the report. Patching blind wastes effort on packages that were never vulnerable in the first place.
curl -fsSL https://scanrook.io/install.sh | sh docker save myapp:current -o myapp-current.tar scanrook scan --file myapp-current.tar --format json --out report.json # List findings by severity and package jq -r '.findings[] | "\(.severity)\t\(.package.name)\t\(.cve)"' report.json | sort
Group the output by package. Findings clustered in a handful of OS packages (openssl, libc, zlib) are almost always base-image findings, not application findings — that is your patch target for the rest of this guide.
Step 2: Rebuild against a fresh pull of the base tag
The single most common reason a base-image CVE stays open is that the image was never rebuilt against a current pull of the tag. Force Docker to check upstream instead of reusing the cached layer:
# --pull: re-check the registry for a newer image under the same tag # --no-cache: rebuild every layer, so package installs re-run against the new base docker build --pull --no-cache -t myapp:patched .
This alone resolves any CVE that the base-image maintainer has already fixed and published under the tag you use. It is free — no Dockerfile changes required — and it is the first thing to try.
Step 3: Apply OS security updates during the build
Even a freshly pulled base can lag a distribution's security feed by a few days. Running the package manager's upgrade command during the build closes that remaining gap:
# Debian / Ubuntu bases
FROM debian:12-slim
RUN apt-get update \
&& apt-get upgrade -y \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Alpine bases
FROM alpine:3.20
RUN apk upgrade --no-cache
# Red Hat / UBI bases
FROM registry.access.redhat.com/ubi9/ubi-minimal
RUN microdnf update -y && microdnf clean allIf your organization requires byte-reproducible builds, this step conflicts with that goal since upgrade pulls whatever is current at build time. In that case, skip this step and rely on Step 2 plus a disciplined rebuild schedule (Step 6) as your patch mechanism instead.
Step 4: Patch packages that have no upstream base-image fix yet
Sometimes the CVE is fixed in a specific package version, but neither Step 2 nor Step 3 picks it up yet — the distribution has not shipped the update to its repositories. In that case, pin the fixed version explicitly:
# Debian/Ubuntu: install an explicit fixed version
RUN apt-get update \
&& apt-get install -y --no-install-recommends openssl=3.0.15-1~deb12u1 \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Alpine: pin to a fixed package version from a specific repo
RUN apk add --no-cache "openssl>=3.3.2-r0"This is a stopgap, not a long-term strategy — explicit version pins fall out of date and need periodic review. Track them in a comment with the CVE ID and revisit when the base image ships the fix natively, then remove the pin.
Step 5: Pin the base image by digest once it is patched
Once you have a build you trust, pin it by digest so the exact same patched layers ship to every environment until you deliberately update again:
# Find the current digest for a tag
docker pull node:22-slim
docker inspect --format='{{index .RepoDigests 0}}' node:22-slim
# node@sha256:9b2e7a3c... (example)
# Pin the Dockerfile to that digest
FROM node@sha256:9b2e7a3c...Digest pinning does not mean freezing forever — it means the update is now a visible, reviewable change in your Dockerfile (a one-line diff) rather than a silent change that happens the next time someone runs a build.
Step 6: Put the rebuild-and-patch cycle on a schedule
Manual patching works once. A schedule is what keeps the count down over time. A minimal CI job that rebuilds, scans, and fails on new criticals:
name: base-image-patch-check
on:
schedule:
- cron: "0 6 * * 1" # every Monday
workflow_dispatch: {}
jobs:
rebuild-and-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: docker build --pull --no-cache -t myapp:ci .
- run: docker save myapp:ci -o myapp.tar
- run: curl -fsSL https://scanrook.io/install.sh | sh
- run: scanrook scan --file myapp.tar --format json --out report.json
- run: |
CRIT=$(jq '.summary.critical' report.json)
if [ "$CRIT" -gt 0 ]; then
echo "::error::$CRIT critical findings after patch rebuild"
exit 1
fiOur GitHub Actions scanning guide covers the full pipeline, including uploading the report as a build artifact and commenting results on pull requests.
Verifying the patch worked
Scan before and after every patch round and diff the severity totals with the same scanner version, so the comparison is apples to apples:
docker save myapp:before -o before.tar scanrook scan --file before.tar --format json --out before.json docker save myapp:patched -o after.tar scanrook scan --file after.tar --format json --out after.json jq '.summary' before.json after.json
If a finding survives the full cycle, it likely has no fix published anywhere yet. At that point the question shifts from patching to triage: is the affected package actually reachable at runtime, and does switching to a smaller base image remove the package entirely instead of waiting on a patch. Our broader guide to reducing CVEs in Docker images covers that path in detail.
Where ScanRook fits
Patch cadence only works if you can measure it. ScanRook scans a saved image tar locally or in CI, matches installed packages against OSV, NVD, and vendor advisory data, and returns severity totals you can gate a build on directly — so the weekly rebuild job above is enforceable, not aspirational. See the docs for CI recipes across common pipelines.
Frequently asked questions
How do I patch vulnerabilities in a Docker base image?
Rebuild with --pull --no-cache, run the package manager's upgrade command during the build, then rescan to confirm the findings dropped.
Does docker pull get the patched image automatically?
Only if you pull the same tag again with --pull. Your local cache otherwise keeps serving the old, unpatched layer.
Should I pin my base image to a digest or a tag?
Pin to a digest for reproducible builds, then update the digest deliberately on a schedule — treat the update as a reviewable one-line diff.
How often should base images be patched?
Weekly at minimum, nightly for internet-facing services. Automate the cycle in CI so it does not depend on someone remembering.
Verify every patch round with ScanRook
Scan before-and-after builds, diff severity totals, and gate CI on the result so your patch cadence is measurable instead of assumed.