Docker audit fix

How to Avoid Floating Tags and Secure Docker Base Images

A floating tag such as :latest is a moving pointer, so the image you tested is not necessarily the one production pulls and a bad upstream push arrives silently. Pin an explicit version tag, or a sha256 digest for full integrity.

Hiren KalariyaLast reviewed: Oct 12, 2026Check base-image-security
Medium
severity
Yes
needs root or sudo
3
results it can return
2 of 8
checks in this audit

Every threshold on this page is transcribed from the docker-security-image-vulnerabilities audit script that CtrlOps runs, and a build check fails if the two ever disagree. See all 8 Image Vulnerabilities checks, or how the audit runs.

Medium severityNeeds sudo

What this check reads

The check lists every tagged local image with docker images --format '{{.Repository}}:{{.Tag}}', drops the <none> entries, and counts what remains as the total. It then filters that same list with grep -E ':(latest)$' and keeps the first 5 matches for the message. The threshold is that literal suffix and nothing else: one image whose tag is exactly latest produces the WARN, a list of images with no such tag is the PASS, and no tagged images at all is the SKIP.

When it applies

Needs the docker binary and daemon access; otherwise SKIP. It lists all tagged local images to count the total, then reports the first 5 whose tag is exactly latest. Two scope limits follow from that: other floating tags - stable, main, edge, 3 as a shorthand for 3.x - are not matched, and an image pinned by digest is indistinguishable from one pinned by version tag, since only the literal latest triggers the finding. It looks at what is on the host, not at what your Dockerfiles say, so a FROM ubuntu:latest that has been built into a locally-tagged image will not be reported.

What each result means

Result thresholds this check applies
ResultWhenWhat it means
SKIPDocker not installed, daemon unreachable, or no local imagesNothing was examined. No image list was produced, so the check makes no claim about how your tags are pinned.
WARNAny image tagged `:latest`At least one local image sits on the floating :latest tag, and the message names up to five of them.
PASSNoneNo local image carries the literal :latest tag. Other floating tags such as stable, main or edge are not covered by this result.

Why it matters

latest is a moving pointer: what you pulled in testing is not necessarily what production runs, so builds are not reproducible and a compromised or broken upstream push propagates silently. Pin a version tag (nginx:1.27.1) or, for full integrity, a digest (nginx@sha256:...). OWASP Docker Cheat Sheet Rule #9 (pin images), CIS Docker 4.2 "Ensure that containers use only trusted base images", SLSA / supply-chain guidance, NIST SP 800-190 4.1.

Why it fails, and when it is wrong

  • Locally built images tagged myapp:latest by default (docker build -t myapp .) trigger this even though they are not pulled from a registry. Tag builds with a version or git SHA as well.
  • Only images on disk are checked; a running container from app:latest whose image was since retagged shows up in the Unpinned Running Images check instead.
  • Pinning by digest requires a process for updates (Renovate/Dependabot) or you freeze security fixes.

How to fix it

List the images on :latest

docker images --format '{{.Repository}}:{{.Tag}}' | grep -v '<none>' | grep ':latest$'

Pin a version or a digest

An explicit version tag makes builds reproducible; a digest also proves the bytes have not changed.

docker image inspect <image> --format '{{index .RepoDigests 0}}'
FROM python:3.12.5-slim-bookworm@sha256:<digest>
image: ghcr.io/org/app:1.4.2

Tag your own builds with a version

docker build -t myapp . tags the result latest by default, which is how most locally built images end up in this finding.

docker build -t myapp:1.4.2 -t myapp:$(git rev-parse --short HEAD) .

Remove the stale tag and automate the bumps

The check reads the local image store, so the WARN stays until the old :latest image is gone.

docker rmi <repo>:latest

A pinned image does not pick up patches on its own. Let Renovate or Dependabot raise the pins, so pinning does not freeze your security fixes.

Verify the fix

docker images --format '{{.Repository}}:{{.Tag}}' | grep -v '<none>' | grep ':latest$'
# expected: no output

# Catch the floating tags this check does not match:
grep -rn '^FROM' Dockerfile */Dockerfile 2>/dev/null
grep -rn 'image:' docker-compose.yml compose.yaml 2>/dev/null

# The strongest form of pinning - by digest:
docker image inspect <image> --format '{{index .RepoDigests 0}}'
# then use FROM node:22-alpine@sha256:... in the Dockerfile

Debugging

Three different cases share this status. 'Docker not installed' is correct on a host with no Docker. 'Cannot query Docker daemon' means the audit account could not reach the daemon: check systemctl is-active docker, then docker ps as that user and sudo docker ps, and run the audit as root or with sudo if only the second works. 'No local images found' means the daemon held no tagged images, which is normal on a host that pulls at deploy time; check with docker images -a. It is also what you get when the audit has root but the daemon is down, because an empty answer and an empty image list look the same to the script. The Docker Inventory check covers the unreachable case in more detail.

A base image pulled as :latest stays in the local store under that tag, and keeps the WARN alive after you have pinned everything. docker rmi <repo>:latest removes one, and docker image prune -a removes every image no container uses (plain docker image prune only removes untagged ones). The durable fix is to pin the tag in the Dockerfile or Compose file so nothing pulls :latest again.

The blind spot. stable, main, edge and truncated version tags all float, and none of them are matched here. Check your FROM lines and image: keys with the greps above.

The message truncates at 5; the full list is the first command above.

Pinned images do not get patches automatically. Pair pinning with a scheduled rebuild and a scanner (see the Image Vulnerability Scan check) or a tool like Renovate/Dependabot that raises the pin deliberately.

Sources

How the script reads this

Next

Re-run the Image Vulnerabilities audit after applying the fix and confirm this check moves to PASS. CtrlOps runs all 8 checks over your existing SSH connection and scores the result, so the change is visible without reading another config file.

All 8 Image Vulnerabilities fixes
Audit your fleet

Find every one of these on every server, in one click

CtrlOps runs this audit over your existing SSH connection - no agents, no scripts to manage. $7/user/month after a 1 month free trial - no credit card required.

Windows

✓ Start instantly·✓ No credit card·✓ No sneaky autorenewals