What this check reads
It asks the first package manager it finds, trying apt-get, then dnf, yum, zypper and apk, which updates it would install, and filters that simulation for docker|containerd|runc. On apt the simulation is apt-get -s -o Debug::NoLocking=true upgrade and the matching ^Inst lines are counted twice: once for the total, and once for the lines that also contain security. No matching line at all is the PASS, one or more security lines is the FAIL, and any other pending line is the WARN. The other package managers return only a total, so on those a pending update can never be worse than a WARN.
When it applies
Runs on any host with a supported package manager (apt-get, dnf, yum, zypper, apk) and the docker binary present; without the binary it SKIPs "Docker not installed", without a package manager it SKIPs too. No root is needed. It filters pending updates for docker|containerd|runc and, as with the database-patch check, reads the cached package lists - a stale apt update gives a stale answer. Only the apt branch distinguishes security updates and can therefore FAIL; on dnf/yum/zypper/apk every pending update is a WARN. Docker installed from a static binary, a Snap, or a convenience script outside the package manager is invisible.
What each result means
| Result | When | What it means |
|---|---|---|
| SKIP | Docker not installed / no package manager | No pending-update list was consulted, so the container stack's patch level is unknown. Either no docker binary is present, or none of apt-get, dnf, yum, zypper and apk was found. |
| PASS | Nothing pending | The package lists on this host show no waiting docker, containerd or runc update. The answer is only as fresh as the last apt update or equivalent refresh. |
| FAIL | apt: security-pocket update pending | A waiting container-stack update is marked as a security fix, the class that includes the runc escapes that give host root. Patch it now. |
| WARN | Other pending updates | Container-stack updates are waiting but none is tagged as a security fix. On dnf, yum, zypper and apk every pending update lands here, security or not, so read the release notes. |
Why it matters
Containers share the host kernel and depend on runc/containerd for isolation. Escapes are real and recurring: runc CVE-2019-5736 (overwrite host runc), CVE-2024-21626 "Leaky Vessels" (fd leak → host filesystem), and in November 2025 three more runc escapes (CVE-2025-31133, CVE-2025-52565, CVE-2025-52881, fixed in runc 1.2.8 and 1.3.3), with containerd and BuildKit CVEs in the same family. CIS Docker 1.1.1/1.2.1 "Ensure the container host has been hardened / Docker is up to date"; NIST SP 800-190 4.5 (host OS).
Why it fails, and when it is wrong
- Docker's official apt repository (
download.docker.com) does not use a-securitypocket, so updates from it always land in WARN, never FAIL, even when they fix a CVE. Read the release notes. - Ubuntu's
docker.iopackage comes fromuniverse/-updates, which sometimes carries security fixes without the-securitytag. - Snap-installed Docker (
snap list docker) and static binaries are not seen. - Stale apt lists (no recent
apt update) produce false PASS. - A PASS is about pending packages, not installed versions. A host whose repository simply has nothing newer, for example a distribution
docker.iofrozen on an old runc, passes while still exposed. Check the installedrunc --versionagainst the advisories as well.
How to fix it
Refresh the package lists
The check reads the cached lists, so a stale cache reports nothing pending.
sudo apt-get update
apt-get -s upgrade | grep '^Inst ' | grep -iE 'docker|containerd|runc'Upgrade the container stack
Upgrade the engine and its runtime together. On Debian and Ubuntu with Docker's repository:
sudo apt-get install --only-upgrade docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-pluginOn RHEL and its rebuilds, sudo dnf upgrade docker-ce docker-ce-cli containerd.io. With the distribution's own packages, upgrade docker.io, containerd and runc instead.
Make sure the daemon runs the new version
docker version --format 'client {{.Client.Version}} server {{.Server.Version}}'
sudo systemctl restart docker # only if the server version is still the old oneA restart stops every running container, and only those with an always or unless-stopped restart policy come back on their own, unless "live-restore": true is set in daemon.json. Live restore only covers patch releases, not a jump to a new major version.
Check runc against the advisories
A PASS means nothing is pending, not that what is installed is safe.
runc --version
# expected: 1.2.8 or later, or 1.3.3 or later on the 1.3 lineOlder than that is exposed to the November 2025 escapes (CVE-2025-31133, CVE-2025-52565, CVE-2025-52881), and older than 1.1.12 to CVE-2024-21626 as well.
Verify the fix
sudo apt-get update
apt-get -s -o Debug::NoLocking=true upgrade | grep '^Inst ' | grep -iE 'docker|containerd|runc'
# expected: no output
# RHEL family, including the security-only view the check cannot give you:
dnf -q check-update 'docker*' 'containerd*' 'runc*'; dnf updateinfo list security
# Installed versions matter more than "no pending updates" - check against known escapes:
docker version --format '{{.Server.Version}}'; runc --version; containerd --version
# runc must be 1.2.8+ or 1.3.3+ (CVE-2025-31133, CVE-2025-52565, CVE-2025-52881)
# A package upgrade is not live until the daemon restarts:
systemctl status docker --no-pager | head -3Debugging
The docker binary is absent, so the check does nothing even if containerd is present and outdated.
Or Docker was installed outside the package manager (static binary, Snap). Track versions manually against Docker's release notes; no package check will see it.
The package lists are probably stale, or the packages are held. sudo apt-get update first, and check apt-mark showhold / dnf versionlock list.
Expected: only the apt branch inspects the archive pocket. Use dnf updateinfo list security to classify it.
The finding clears as soon as the packages are installed, because it reads pending packages, not the running daemon. If it persists, the package lists or a hold are the cause (see above). If it has cleared but docker version still reports the old server version, the daemon was not restarted onto the new binaries: run sudo systemctl restart docker. That stops every running container, and only those with an always or unless-stopped restart policy come back on their own, unless "live-restore": true is set in daemon.json. Live restore only carries containers across patch releases of the daemon, not across a jump to a new major version. Containers already running keep the runtime they started with until they are restarted.
runc and containerd bugs are container escapes: CVE-2019-5736, CVE-2024-21626 and the three November 2025 runc advisories all give host root from inside a container, regardless of how carefully the container itself is configured. Patch these ahead of application updates.
Sources
- Docker Engine release notes
- Docker: Install Docker Engine on Ubuntu / Debian / RHEL (repo setup)
- Docker: Live restore
- runc GHSA for CVE-2024-21626
- runc GHSA for CVE-2025-31133
- Snyk: Leaky Vessels container breakout vulnerabilities
- containerd security advisories
How the script reads this
Next
Re-run the Network & Supply Chain audit after applying the fix and confirm this check moves to PASS. CtrlOps runs all 5 checks over your existing SSH connection and scores the result, so the change is visible without reading another config file.
All 5 Network & Supply Chain fixes