# how to check container base images for CVEs

## TL;DR

List the base images in your Dockerfiles, scan them with trivy or docker scout, and separate base-layer findings from your app-layer findings. When the CVE is in the base, the fix is a newer base tag plus a rebuild, not a patch to your code.

```text
how to check container base images for CVEs
```

## Use this when

- A CVE is announced in a common base image (Debian, Alpine, Ubuntu, distroless)
- You are hardening images before production or a compliance review
- Your scanner flags OS-package CVEs and you need to know which layer they come from
- You want base-image scanning added to the build pipeline

## Not for this skill when

- The findings are all in your application dependencies (scan the lockfile instead)
- You need runtime protection for running containers (different tooling)
- You are choosing a base image from scratch (that is an architecture decision, this is a scanning workflow)

## Steps

### 1. List every base image you actually use

Grep the FROM lines across all Dockerfiles. Repos accumulate Dockerfiles, and the one you forgot is the one with the ancient base.

```bash
grep -rn "^FROM" --include="Dockerfile*" .
```

Expected: a complete list of base images and tags. Pin down any `latest` tags while you are here; unpinned bases make every future scan a surprise.

### 2. Scan the base image directly

Scan the base tag itself to see what it ships with, separate from your app layers.

```bash
trivy image [your-base-image]:[tag]
```

Expected: a CVE list for the base image alone. Alternatives: `docker scout cves [image]` or `grype [image]`. Compare scanners occasionally; their databases differ and one may know a CVE the other misses.

### 3. Separate base-layer from app-layer findings

Scan your built image and diff against the base scan. Findings present in both live in the base; findings only in the built image are yours.

Expected: two buckets with different owners and different fixes. Base-layer CVEs get fixed by the base maintainer and picked up via rebuild; app-layer CVEs are your dependency upgrades.

### 4. Bump the base tag and rebuild

Move to the patched base tag, rebuild from scratch with no cache for the base stages, and re-scan.

```bash
docker build --no-cache -t [your-image]:[new-tag] .
```

Expected: the re-scan shows the base-layer CVEs gone. If they persist, the base maintainer has not shipped a fix yet; check their security tracker and consider a slimmer base in the meantime.

### 5. Put the scan in the pipeline

Fail the build on new fixable critical CVEs in the base, and alert (not fail) on the rest. Rebuild images on a schedule even when your code has not changed, because bases rot.

Expected: CI catches the next base-image CVE at build time instead of in production. Scheduled rebuilds, weekly or so, keep the base fresh without manual effort.

### Variant: distroless and minimal bases

Minimal bases have tiny CVE lists, which is the point, but the scanner still needs the right database for them. If trivy reports almost nothing on a distroless image, verify the scanner supports that base rather than assuming you are clean.

### Variant: air-gapped or proxied environments

Scanners need their vulnerability databases refreshed. Mirror the database internally or allow the scanner's update endpoint through the proxy; a stale database is a false sense of security.

## Why this happens

Base images are full Linux distributions with hundreds of OS packages, and CVEs land in those packages constantly. Your application code can be perfect and the image still ships known vulnerabilities, which is why the base gets its own scan and its own rebuild cadence.

## Edge cases and pitfalls

- Multi-stage builds: scan the final stage's base, not just the build stage.
- `latest` tags: the scan result is a moving target; pin to digest for reproducibility.
- Unfixable CVEs in the base: some have no patched package yet; track them and move on, do not block every build forever.
- Scanner database lag: a CVE announced today may not be in the database until tomorrow.
- Language-specific packages in the base (like a Python in a slim image): those are base-layer too, fix via the base tag.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_pt8QCsGchwMpUTmsh4k6ZA
