Evergreen

Logo

Local-first deprecation intelligence for software repositories.

View the Project on GitHub arminazimi/evergreen

0007 — Detect end-of-life container base images from Dockerfiles and Compose files

Status: accepted

Evergreen will add a container detector that reads base-image references from repository files and reports the ones whose image release line has reached a vendor-published end of life. It follows the existing detector SDK and the v2 registry; it adds no new engine behavior.

What the detector reads

Declaration Files Deterministic?
FROM <image>[:<tag>][@<digest>] Dockerfile, Dockerfile.*, *.Dockerfile, Containerfile, Containerfile.* Yes, when the reference is a literal
FROM --platform=<p> <image>… same Yes; the flag is skipped, the image is read
FROM <image> AS <stage> same Yes; the stage name is ignored
FROM <stage> where <stage> was defined earlier in the same file same Not an image; skipped (the earlier FROM is the declaration)
image: <image>[:<tag>][@<digest>] under services compose.yaml, compose.yml, docker-compose.yaml, docker-compose.yml, docker-compose.*.yml Yes, when the value is a literal

Image references are parsed into registry, repository, tag, and digest. The version is taken from the tag only. Official images that carry a vendor lifecycle (node, python, postgres, mysql, redis, and similar) are matched by repository name on Docker Hub (node, library/node, docker.io/library/node); images on other registries are matched only when a rule names that exact registry and repository.

What is dynamic, and what happens to it

A reference is dynamic when its image or tag cannot be known without running something:

Dynamic references produce no finding and no guess. Where the engine already reports unclassified references (ScanResult.unclassified), a dynamic reference to a lifecycle-tracked repository is reported there with its file and line, so the reader can see it was seen and not judged. Evergreen does not substitute a build-arg default: the default is a repository value, but the value that is actually built is decided by the build command, which Evergreen does not see.

A digest-only reference (node@sha256:…) has no tag and is unclassified. A tag plus digest (node:20-bookworm@sha256:…) is classified by its tag; the digest is recorded as evidence and never used for lifecycle.

How tags map to versions

A tag is classified only if its leading component is a version of the image’s release line and the rest is a known variant suffix. node:18, node:18.20, node:18.20.4, node:18-alpine, and node:18.20.4-bookworm-slim all belong to release line 18. node:latest, node:alpine, and node:lts-alpine belong to none and are dynamic. A tag that starts with a version but ends in something Evergreen does not recognize (node:18-custom-build) is treated as unclassified rather than assumed to be Node 18.

Where lifecycle facts come from

Allowed sources, each recorded in the rule’s provenance:

Rejected inputs, which are never used to produce a rule or a finding:

Where it looks

The detector walks the repository (eight directories deep, at most 2,000 matching files, never following symbolic links) and skips dependency, build, and test-fixture directories: .git, node_modules, vendor, dist, build, target, .venv, venv, fixtures, __fixtures__, and testdata. Images in test fixtures are examples, not something the repository ships.

Out of scope for the first version

Examples for the fixture task

True positives (rule exists for the release line, tag is literal):

Must not produce findings:

Malformed: a Dockerfile with a truncated FROM and an unclosed line continuation, and a Compose file that is not valid YAML. Both must produce no findings and no crash.

Consequences