Departments / security / container-scan

container-scan

Use when the user asks to "scan this container", before a registry push, or as a release gate for a Docker/OCI image. Runs Trivy image + filesystem + secret scans, verifies distroless/non-root user, checks Dockerfile best practices, and blocks on Critical/High CVEs.

Department

Security

Safety

safe
Safe · read-only

Supported stacks

Stack-agnostic — no detection required.

When to use

Trigger on any of:

Do not use for running-container runtime monitoring (that is Falco/EDR territory) or for dependency scanning of the source tree pre-build (use dependency-audit).

Inputs

Outputs

Tool dependencies

Procedure

  1. Validate inputs. If IMAGE not provided and DOCKERFILE is, build first: docker build -t scan-target:tmp -f "$DOCKERFILE" "$CONTEXT".
  2. Pull the image if remote: docker pull "$IMAGE" (or podman pull).
  3. Inspect: docker image inspect "$IMAGE" > "$OUT_DIR/image-inspect.json". Parse Config.User, Config.ExposedPorts, RootFS.Layers, History[].CreatedBy.
  4. Trivy vulnerability scan:
    trivy image \
      --format json --output "$OUT_DIR/trivy-image.json" \
      --severity CRITICAL,HIGH,MEDIUM,LOW \
      --vuln-type os,library \
      --scanners vuln \
      ${IGNORE_UNFIXED:+--ignore-unfixed} \
      "$IMAGE"
  5. Trivy secret scan (examines each layer):
    trivy image --scanners secret \
      --format json --output "$OUT_DIR/trivy-secret.json" "$IMAGE"
  6. Trivy config scan (Dockerfile + embedded IaC):
    trivy config --format json --output "$OUT_DIR/trivy-config.json" \
      ${DOCKERFILE:+"$(dirname "$DOCKERFILE")"} \
      ${DOCKERFILE:-"$IMAGE"}
  7. If DOCKERFILE provided, run hadolint: hadolint --format json "$DOCKERFILE" > "$OUT_DIR/hadolint.json".
  8. Best-practice checks against image-inspect.json:
    • Config.User must be non-empty and not root / 0 (else FAIL).
    • RootFS.Layers count should be <= 20 (advisory).
    • Base image SHOULD be distroless, Chainguard Wolfi, or *-slim — heuristic: missing /bin/sh, /bin/bash, or no shell in manifest.
    • No ADD http:// URLs in history (use COPY + verified artefact).
    • No secrets in Config.Env (cross-check against trivy-secret.json).
    • Healthcheck defined (Config.Healthcheck) — advisory.
  9. Optional SBOM: syft "$IMAGE" -o cyclonedx-json > "$OUT_DIR/sbom.cdx.json".
  10. Aggregate and write container-scan-report.md:
    • Gate table: Critical CVE, High CVE, Secrets in layers, Non-root, Dockerfile issues (each row: status + count).
    • Findings grouped by severity with fix-version hints.
    • Upgrade plan: which base-image bump clears the most CVEs.
  11. Exit non-zero if any gate in $FAIL_ON failed.

Examples

Example 1 — registry-ready check

IMAGE=ghcr.io/acme/api:1.14.2 \
OUT_DIR=/tmp/cs-api \
FAIL_ON=CRITICAL,HIGH \
./run-container-scan.sh

Expected container-scan-report.md gate table:

| Gate                       | Status | Count |
|----------------------------|--------|-------|
| Critical CVE               | FAIL   | 1     |
| High CVE                   | WARN   | 3     |
| Secrets in layers          | PASS   | 0     |
| Non-root user (UID != 0)   | PASS   | 1001  |
| Distroless base heuristic  | PASS   | yes   |
| Dockerfile lint            | PASS   | 2 info|

Exit: 2 (Critical CVE gate failed)

Example 2 — Dockerfile linting prior to build

DOCKERFILE=./Dockerfile CONTEXT=. \
OUT_DIR=./scan \
./run-container-scan.sh

Expected excerpt:

[container-scan] building scan-target:tmp
[container-scan] hadolint: DL3008 Pin versions in apt-get install (line 7)
[container-scan] hadolint: DL3025 Use arguments JSON notation for CMD
[container-scan] trivy image: 0 critical, 0 high, 4 medium (alpine 3.19.1)
[container-scan] user=appuser (uid=1001). PASS

Constraints

Quality checks

Customise for your organisation

container-scan

The LLM will rewrite this skill for your environment. Your API key and form inputs stay in your browser — only the skill and your environment go to OpenRouter.

One line. Be specific — cloud, language, framework, orchestrator.

Free text that steers the rewrite. Leave blank if nothing specific.

cost estimate: