Safeguard
Container Security

How Snyk Container detects a Dockerfile's base image with...

Snyk Container identifies a Dockerfile's true base image by comparing layer digests against a registry database, no docker run required.

Karan Patel
Cloud Security Engineer
8 min read

Every container image scan starts with the same question a human reviewer would ask first: what is this thing actually built on? A Dockerfile might say FROM node:18-alpine, but by the time that image is pushed to a registry, "18-alpine" could point to any of dozens of underlying builds depending on when it was resolved. Snyk Container answers this question without pulling gigabytes of layer data and without executing a single instruction inside the image. It reads the image the way a registry reads it: as a manifest, a small JSON config blob, and an ordered list of layer digests. That metadata alone is enough to fingerprint the base image and flag it as outdated. Here is how that mechanism works mechanically, using Snyk's own publicly documented description of the feature, and where its limits are.

What does "base image detection" mean in a container scan?

It means identifying the specific published image — vendor, repository, and version — that a given container image was originally built from, using evidence inside the image itself rather than trusting the Dockerfile's FROM line as the last word. Snyk's documentation for Snyk Container describes comparing the image under test against a maintained catalog of public base images — Docker Official Images, distro images like debian and alpine, and popular language runtimes like python, node, and golang — so it can report both "what you're running" and "what you should switch to" as part of its base image remediation advice. This matters because a FROM line is a request evaluated at build time, not a permanent record of what got baked into the artifact. node:18-alpine resolved on a build server in January and the same tag resolved again in June can produce two images with different underlying packages and different CVEs, even though the Dockerfile text never changed.

How can a scanner identify a base image without running the container?

It reads the OCI or Docker image manifest and config blob over the registry API instead of pulling and executing the full filesystem. Every image pushed to a registry implementing the Docker Registry HTTP API V2 (formalized today as the OCI Distribution Specification) exposes a manifest describing the image as an ordered list of layer digests, plus a separate, much smaller config blob — typically a few kilobytes, versus individual layers that can run into hundreds of megabytes. That config blob contains a rootfs.diff_ids array (the SHA256 digests of each layer in uncompressed form) and a history array recording the build instructions behind each layer, including auto-generated comments Docker itself inserts, such as # (nop) ADD file:... in /. A scanner can retrieve just the manifest and config with a couple of HTTP GET requests and never touch the actual layer contents, meaning it never needs docker pull, docker run, or code execution inside the target image to reason about what produced it.

Why compare layer digests instead of just reading the FROM line?

Because layer digests are cryptographic and effectively immutable, while tags are just mutable pointers a registry can reassign at any time. Docker builds images by stacking layers on top of one another; if a Dockerfile starts with FROM python:3.11-slim and nothing before your own COPY or RUN instructions modifies the filesystem, the first several layers of the final image are bit-for-bit identical to the base image's layers — same uncompressed content, same SHA256 digest. Base image detection logic can exploit this by taking the ordered list of digests from an image's config and comparing it against a precomputed index of digest sequences for every public base image and tag it tracks. Where the sequences match, up to and including the longest common prefix, the base image is identified with high confidence, because an accidental collision on a full layer's SHA256 digest for unrelated content is not a realistic risk. That is inherently more trustworthy than parsing a human-readable string, since a tag carries no cryptographic guarantee about what it resolves to on any given day, and registries are free to move tags like latest or even pinned-looking version tags to new content.

What happens when the tag has moved or the Dockerfile is unavailable?

Digest-based identification still works, because it never depends on the tag string or on having source access to the Dockerfile at all. If a scan target is a myapp:latest image pulled straight out of a registry with no Dockerfile in hand — the common case when scanning a running deployment or a registry inventory rather than a source repo — the scanner can still walk the config's diff_ids and history entries to work out which base image and version produced the earliest layers, then report that, for example, the image was actually built on alpine:3.18.4 even though nothing in the available metadata says so in plain text. This is also why base image recommendations can be attached to images scanned directly out of Amazon ECR, Google Artifact Registry, or Docker Hub with no CI context supplied at all — the identification happens from the pushed artifact, not from build instructions a human wrote days or months earlier and that may no longer match what actually got published.

How does base image identification turn into a remediation recommendation?

Once the base image and its version are known, Snyk cross-references the vulnerabilities inherited from that base against newer tags in the same image family and surfaces options with fewer known CVEs, split into minor updates (same major lineage, e.g. node:18.18 to node:18.20), major updates (a version jump, e.g. node:18 to node:20), and alternative images entirely (e.g. moving to an Alpine or distroless variant). Public descriptions of this feature — marketed by Snyk as base image remediation — note that it favors same-lineage upgrades first, since those carry a lower expected risk of breaking application behavior, before surfacing a bigger jump as a secondary option. The whole recommendation depends on the identification step running correctly first: if the wrong base image or wrong version is identified, the vulnerability delta and the suggested fix are both meaningless, which is exactly why the digest-matching work has to be reliable before any advice gets generated on top of it.

Where does static base image detection fall short?

It struggles whenever the layer history it depends on has been altered, squashed, or built from something outside its catalog. Multi-stage Dockerfiles that copy compiled artifacts into a fresh final stage — FROM scratch or a minimal distroless image — can legitimately have no matching public base in a vendor's catalog, since distroless images are intentionally stripped down and vary release to release. Custom, internally maintained base images — the kind most mature engineering organizations actually standardize their fleets on — won't match a public catalog at all unless a vendor has explicitly indexed that specific private image, which most scanners cannot do without direct access to it. Layer squashing (docker build --squash, or copying files across build stages instead of inheriting layers) can also collapse the very digest boundaries the matching technique relies on, since a merged layer no longer shares content, or a digest, with any individual upstream layer. These are documented, expected boundaries of a technique that trades some coverage for the significant benefit of never having to execute untrusted image content just to identify it.

How Safeguard Helps

The mechanics above point at a gap worth naming directly: public-catalog digest matching is genuinely effective for identifying known, publicly published base images, but it has little to say about the private and custom base images most organizations actually standardize their fleets on. Safeguard approaches base image identity as provenance data that gets established at build time and carried forward, rather than re-derived by best-effort comparison later. Safeguard records the full lineage of every image built through a monitored pipeline — which base image and digest an artifact actually descended from — as part of its SBOM and attestation data, so "what is this built on" is answered with a signed record instead of a layer-matching guess made after the fact. For internal and hardened base images that public catalogs can't recognize at all, Safeguard lets teams register their own base image inventory and enforce policy against it, flagging or blocking builds that drift onto an unapproved or stale base before an image ever reaches a registry. Layered on top of continuous CVE monitoring against the base images an organization actually uses — public and private alike — this closes the coverage gap that any public-database approach inevitably leaves at the edges: your own internal registries, your own hardened images, and your own definition of what "current" should mean.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.