Back to Tutorials

Security Scan an Image

Run vulnerability scans on Docker images, interpret severity levels, apply remediation in running containers, and export Dockerfile fixes.

Prerequisites

  • Docker is running
  • At least one image exists locally
  • Pro subscription required
  • Trivy is installed (brew install trivy) — if not installed locally, Zenithal uses the Trivy bundled inside the Lima VM

Scenario 1: Scan an Image for Vulnerabilities

  1. Click Image Library in the sidebar
  2. Hover over an image row (e.g., node:20-alpine) and click the shield icon ("Security Scan")
  3. The scan begins — Trivy analyzes image layers for known vulnerabilities (CVEs)
  4. A progress indicator shows the scan running
  5. When complete, the results view opens with a severity breakdown

What You'll See

  • Summary header — Overall risk level, vulnerability counts by severity, detected OS, scanner name, and scan duration
  • Vulnerability list (left panel) — Sorted by severity, each showing CVE ID, CVSS score, package name, installed version, and fixed version
  • Detail panel (right panel) — Full CVE description, affected package details, references, published date, and remediation options
  • Rescan button — Re-run the scan after making changes (uses cached Trivy DB for consistent results)
  • Save Dockerfile Fix (All) button — Export a Dockerfile that upgrades all packages

Reading Scan Results

Critical (Red)

Actively exploited vulnerabilities or those with public exploits. Update immediately — these pose an immediate risk.

Example: CVE-2024-3094 — XZ Utils Backdoor (CVSS 10.0)

High (Orange)

Serious vulnerabilities that could be exploited with some effort. Update in the next maintenance window.

Example: CVE-2024-21626 — Container escape via runc

Medium (Yellow)

Vulnerabilities requiring specific conditions to exploit. Plan to address in upcoming updates.

Example: CVE-2023-44487 — HTTP/2 Rapid Reset attack

Low (Blue)

Minor vulnerabilities with limited impact. Address as part of regular maintenance.

Example: CVE-2023-29491 — ncurses memory corruption

Result Details

Each vulnerability entry shows:

  • CVE ID — the Common Vulnerabilities and Exposures identifier
  • CVSS Score — numeric severity rating (0–10) with color-coded gauge
  • Package — the affected OS or application package
  • Installed Version — the currently installed version
  • Fixed Version — the version that resolves the issue (if available)
  • Severity — Critical, High, Medium, or Low
  • Overview — brief summary of the vulnerability
  • References — links to security advisories (NVD, vendor pages)
  • Published Date — when the CVE was disclosed

Scenario 2: Run a Fix in a Container

For quick testing, you can run remediation commands directly in a running container:

  1. Scan an image and select a vulnerability with a fix available
  2. In the Remediation section, you'll see two options:
    • Safe (green badge) — Upgrades all packages. Always works, recommended approach
    • Specific (orange badge) — Upgrades only the affected package. More targeted but may fail if package names differ between distros
  3. If the image has running containers, select one from the container picker
  4. Click the terminal icon next to a command — Terminal.app opens with docker exec running the fix
  5. The command runs as root (package managers require it) and drops you into a shell when done

Cross-OS Adaptation

Zenithal automatically adapts commands when the scan detects a different OS than what's running in the container:

  • If Trivy reports Debian but the container runs Alpine, apt-get commands are converted to apk
  • Package names are also translated (e.g., xz-utils xz, libssl-dev openssl-dev)
  • A warning toast appears if there's an OS mismatch

Important: Container changes are ephemeral — they're lost when the container stops. For permanent fixes, use the Dockerfile export (Scenario 3).

Scenario 3: Export a Dockerfile Fix (Single CVE)

  1. Select a vulnerability with a fix available
  2. Click Save Dockerfile Fix in the remediation section
  3. Choose a save location — the file is named Dockerfile.fix-<cve-id>
  4. The generated Dockerfile:
    • Starts with FROM <scanned-image> (valid, buildable Dockerfile)
    • Detects the image's actual OS (reads /etc/os-release) to use the correct package manager
    • Includes an "upgrade all" command (recommended) and a commented "upgrade specific package" alternative
    • Translates package names if the image OS differs from what Trivy reported

Example Output (Alpine)

Dockerfile.fix-CVE-2024-3094
# Fix for CVE-2024-3094: Update xz-utils
# Severity: CRITICAL
# Required version: >= 5.6.1
#
# Note: This upgrades system packages in the image. Vulnerabilities baked
# into the base image layers (e.g. compiled binaries, language runtimes)
# may persist. If the issue remains after building, update the base image
# in your original Dockerfile (e.g. FROM node:22-alpine).

FROM my-app:latest

# Upgrade all packages (recommended):
RUN apk update && apk upgrade --no-cache

# Or upgrade a specific package:
# RUN apk add --upgrade --no-cache xz

Scenario 4: Export a Dockerfile Fix (All Vulnerabilities)

  1. After a scan completes, click Save Dockerfile Fix (All) in the summary header
  2. Choose a save location — the file is named Dockerfile.security-fix
  3. The generated Dockerfile lists all CVEs as comments and includes a single upgrade-all command

Example Output

Dockerfile.security-fix
# Security Fix Dockerfile — my-app:latest
# Generated by Zenithal Security Scanner
# Fixes 7 vulnerabilities (2 critical, 2 high, 2 medium, 1 low)
#
# Note: This upgrades system packages in the image. Vulnerabilities baked
# into the base image layers (e.g. compiled binaries, language runtimes)
# may persist. If issues remain after building, update the base image
# in your original Dockerfile (e.g. FROM node:22-alpine).
#
# CRITICAL:
#   CVE-2024-3094 — xz-utils 5.6.0 → 5.6.1
#   CVE-2024-21626 — runc 1.1.9 → 1.1.12
# HIGH:
#   CVE-2023-44487 — golang.org/x/net 0.14.0 → 0.17.0
#   CVE-2023-39325 — golang.org/x/net 0.14.0 → 0.17.0

FROM my-app:latest

RUN apk update && apk upgrade --no-cache

Scenario 5: Build the Fixed Image

  1. Export a Dockerfile fix (Scenario 3 or 4)
  2. Go to Image Library → click Build (hammer icon)
  3. In the Configuration tab:
    • Set Context Path to the directory containing the exported Dockerfile
    • Set Dockerfile Path to the exported file
    • Enter an Image Name (e.g., my-app-fixed) and Tag (e.g., latest)
  4. Click Build Image
  5. After the build completes, scan the new image to verify fixes were applied

See the Build a Docker Image tutorial for a detailed walkthrough of the image build process.

Understanding Base Image Layer Vulnerabilities

When Zenithal detects that the image's actual OS differs from what the vulnerability scanner reports, an orange warning banner appears:

"Vulnerabilities detected in base image layers (Debian). This image runs Alpine. Package upgrades may not resolve these issues — update the FROM image in your original Dockerfile."

Why This Happens

Many Docker images use multi-stage builds. For example, node:20-alpine is an Alpine image, but the Node.js binary inside it was compiled on Debian. Trivy scans all image layers and finds Debian package metadata baked into the compiled binary's layers.

Running apk upgrade (Alpine's package manager) upgrades Alpine packages, but cannot fix vulnerabilities in Debian artifacts embedded in the Node.js binary. These are two different package ecosystems.

What To Do

Vulnerability TypeCan upgrade fix it?Real Fix
OS system packages (same OS)YesRUN apk upgrade / apt-get upgrade
Base image layer packages (different OS)NoUpdate FROM image to a newer version
Language runtime (Go, Node.js)NoUpdate runtime version (e.g., FROM node:22-alpine)
App dependencies (npm, pip, go modules)NoUpdate in package.json, requirements.txt, go.mod
Container runtime (runc, containerd)NoUpdate Docker/Lima on the host

Per-Vulnerability Warning

Each vulnerability in the remediation panel also shows the warning individually:

Base Image Vulnerability — This vulnerability is in the base image layers (Debian), not in the image's own packages (Alpine). Package upgrades won't fix it — update the FROM image in your original Dockerfile instead.

Tips

  • Alpine-based images generally have fewer vulnerabilities due to their smaller package set
  • New CVEs are published daily — rescan after pulling updated images
  • After the first scan, Zenithal uses --skip-db-update for consistent results within a session
  • Vulnerabilities are deduplicated by CVE ID + package name to avoid repeated entries
  • The Image Build view auto-sanitizes image names (strips spaces and invalid characters)
  • Use multi-stage builds to minimize packages in your final image, reducing the attack surface
  • Docker Hub official images are regularly updated with security patches — prefer them over community images

Related Tutorials