Lightweight, self-contained container image vulnerability scanning helper designed to drop into an existing Docker / docker‑compose environment and regularly email a concise vulnerability summary. It wraps Trivy to scan each unique image referenced by your running (or all) containers, aggregates severity counts, renders plain text + optional HTML + per‑image Markdown/JSON artifacts, and sends them via SMTP. If no vulnerability meets your configured severity threshold the run exits quietly (no spammy “all clear” messages).
Core goals:
- Zero persistent state – every run is ephemeral.
- Scan only what matters (deduplicate images, optional compose‑project scoping, exclusion labels).
- Signal over noise – single email with summarized counts + detailed attachments only when something crosses your threshold.
- Simple deploy – single container, minimal required env vars.
If you already have a compose stack, add a service (example snippet):
services:
docktor:
image: your-registry/docktor:latest # or build: .
build: .
environment:
SMTP_HOST: "smtp.mail.local"
SMTP_PORT: "587"
SMTP_USER: "scanner"
SMTP_PASS: "changeme"
MAIL_FROM: "[email protected]"
MAIL_TO: "[email protected],[email protected]"
MIN_NOTIFY_SEVERITY: "MEDIUM" # optional (default LOW)
SCAN_SCOPE: "COMPOSE" # optional (default ALL)
volumes:
- /var/run/docker.sock:/var/run/docker.sock:roThen run:
docker compose run --rm docktorChange SCAN_SCOPE to ALL to scan every container visible to the Docker daemon (subject to ONLY_RUNNING and exclusion labels). COMPOSE restricts scanning to only the containers that belong to the same docker-compose project as the docktor service itself.
Complete list (see app/settings.py). Defaults shown in parentheses.
| Name | Type | Default | Description |
|---|---|---|---|
SMTP_HOST |
str | smtp.example.com |
SMTP server hostname |
SMTP_PORT |
int | 587 |
SMTP port |
SMTP_USER |
str | `` (empty) | SMTP username (optional) |
SMTP_PASS |
str | `` (empty) | SMTP password (optional) |
SMTP_USE_SSL |
bool | true |
If true issue STARTTLS before sending (set false to skip) |
MAIL_FROM |
str | [email protected] |
From address |
MAIL_TO |
CSV str | [email protected] |
Comma separated recipients |
TRIVY_BIN |
str | trivy |
Trivy executable name/path |
TRIVY_ARGS |
str | --severity HIGH,CRITICAL --ignore-unfixed --timeout 5m |
Extra Trivy flags appended to trivy image |
EXCLUDE_LABEL |
key=value | docktor.ignore=true |
Containers with this label are skipped |
ONLY_RUNNING |
bool | true |
If true, ignore stopped containers |
ATTACH_JSON |
bool | true |
Attach raw JSON scan outputs per image |
LOG_LEVEL |
str | INFO |
Python logging level |
MIN_NOTIFY_SEVERITY |
str | LOW |
Minimum severity that triggers email + inclusion in summaries |
SCAN_SCOPE |
str | ALL |
ALL = every visible container, COMPOSE = restrict to own compose project |
PREVENT_REDUNDANT_REPORTS |
bool | false |
If true, email only when scan results differ from the previous run |
STATE_FILE |
str | /tmp/docktor_state.json |
Path to the JSON file storing the last results hash |
REGISTRY_CACHE_FILE |
str | /tmp/docktor_registry_cache.json |
Path to persist registry digests between runs |
REGISTRY_CACHE_TTL_SECONDS |
int | 21600 |
Seconds to reuse cached registry digests before refreshing |
REGISTRY_RATE_LIMIT_BACKOFF_SECONDS |
int | 900 |
Cooldown duration after a registry 429 response |
REGISTRY_AUTH_USERNAME |
str | `` (empty) | Optional registry username (recommended for Docker Hub to avoid 429s) |
REGISTRY_AUTH_PASSWORD |
str | `` (empty) | Optional registry password or token |
REGISTRY_AUTH_SERVER |
str | https://index.docker.io/v1/ |
Registry server URL passed to docker login |
Convenience examples:
export MIN_NOTIFY_SEVERITY=HIGH # suppress mail unless >= HIGH
export SCAN_SCOPE=COMPOSE # only this compose project
export TRIVY_ARGS="--severity CRITICAL --timeout 3m" # faster, only CRITICAL
export EXCLUDE_LABEL="custom.ignore=true" # redefine exclusion labelExclusion label logic: a container is skipped if either it has docktor.ignore=true OR it matches the dynamic EXCLUDE_LABEL key/value.
Notification suppression: if no vulnerability with severity >= MIN_NOTIFY_SEVERITY is found across all scanned images, the run exits without sending email (attachments are discarded after process exit).
check_image_update consults remote registries to compare local digests. Docker Hub enforces aggressive anonymous limits, so Docktor caches remote digests in REGISTRY_CACHE_FILE for REGISTRY_CACHE_TTL_SECONDS seconds and automatically backs off further lookups for REGISTRY_RATE_LIMIT_BACKOFF_SECONDS after any 429 response. Provide REGISTRY_AUTH_USERNAME/REGISTRY_AUTH_PASSWORD (Docker Hub credentials or token) to authenticate the Docker client and greatly expand the allowed request budget.
The project uses structured types instead of loose dictionaries:
domain_types.ScanResult: dataclass representing a scanned image resultdomain_types.Vulnerability:TypedDictsubset of Trivy vulnerability fields actually consumeddomain_types.Attachment: tuple alias(filename, path, mime)domain_types.ReportBodies: tuple alias(text_body, optional_html_body)
Static analysis is configured via mypy.ini (strict-ish settings). To run:
python -m mypy appmypy is declared in requirements.txt (optional). You can omit installation if you don't need static checks.
- Enumerate containers ->
scanner.enumerate_containers - Scan unique images ->
scanner.scan_all - Render reports ->
reporting.render_reports - Build attachments ->
reporting.build_attachments - Send email ->
email_utils.send_email_with_attachments
Use SCAN_SCOPE to control which containers are enumerated:
ALL(default): scan every container visible to the Docker daemon (subject toONLY_RUNNINGand exclusion labels).COMPOSE: restrict scanning to only the containers that belong to the same docker-compose project as thedocktorservice itself. This relies on the standard labelcom.docker.compose.project. If the project label cannot be resolved (e.g., running outside docker-compose) the scanner falls back to scanning all containers and logs a warning.
Example:
export SCAN_SCOPE=COMPOSE # Only scan containers from the same compose projectSet MIN_NOTIFY_SEVERITY (default: LOW) to control when an email is sent and which vulnerabilities are displayed in the email bodies and per-image markdown attachment tables. If no vulnerability at or above the threshold is found, the scan exits without sending an email (JSON artifacts may still be created transiently during the scan).
Severity ordering (lowest -> highest): UNKNOWN, LOW, MEDIUM, HIGH, CRITICAL.
Example:
export MIN_NOTIFY_SEVERITY=HIGH # Only HIGH and CRITICAL vulns trigger mail and appear in summariesSee CONTRIBUTING.md for development setup, style guidance, and PR checklist.
Released under the MIT License. See LICENSE for details.
Happy scanning!
