Safeguard
Developer Tools•

Scan container images from the terminal with the Safeguard CLI

Scan a container image with the Safeguard CLI from a registry tag or local archive, read vuln.json under ./output, then fix the base image and rescan.

Safeguard Security Team
17 min read

To scan a container image from the terminal, install the Safeguard CLI and run safeguard scan --image nginx:latest, or point it at a private registry, a Podman image or a saved archive. It builds an SBOM, matches it against vulnerability databases and writes vuln.json under ./output. Then ask the assistant to move you to a better base image.

You do not have to drop the AI plan you already use to work this way. Bring the Claude, ChatGPT or Codex plan you already pay for and use it inside the Safeguard CLI. Safeguard adds no extra charge for that usage; your plan's own usage limits and terms still apply. If you would rather ask from a chat window, connect Claude or ChatGPT to the Safeguard MCP server and query the same findings there.

This guide covers why the image deserves its own scan, every image source the CLI accepts, how to read the results with jq, and a worked session that replaces a base image with the assistant's help, plus the mistakes that waste the most time.

Why scan the image, not only the repository

A repository scan reads your manifests and lockfiles. A container image contains more than that: the operating system packages from the base image, anything a RUN step installed, and your application with its dependencies. Findings can come from layers your team never wrote.

Three facts from Docker's own build guidance shape how you should scan.

Tags move. A tag such as 3.21 can point to a different image over time, so scanning myapp:latest today and again next week may not cover the same bytes. Scan the exact image you ship, and pin base images when you need a reproducible build.

Images are snapshots. An image does not pick up patches by itself. Docker recommends rebuilding regularly, with --pull to fetch a newer base image even when one is cached. An image that was clean when built can collect findings while it sits in a registry, because new advisories keep being published against the packages inside it.

Smaller images carry less. Docker's guidance is to start from a trusted source and keep the base image small, because a smaller image minimises the vulnerabilities introduced through dependencies. A fuller image for building and testing plus a slimmer one for production is the pattern it suggests.

So the job is not one scan. It is scan what you ship, fix the base, rebuild, and scan again on a schedule.

Do the work properly with the Safeguard CLI

The Safeguard CLI scans code, container images and SBOMs on your own machine, sends only the results to the platform, and includes an assistant that can fix what it finds. For images, that means one tool reads the image, explains the findings and edits your Dockerfile, with your approval.

Install

On macOS and Linux:

curl -fsSL https://cli.safeguard.sh/install | bash

On Windows, in PowerShell:

irm https://cli.safeguard.sh/install.ps1 | iex

It is a single static binary, with no Node, Python or JVM needed. It runs on macOS, Linux and Windows, on x86_64 or arm64; Windows is x86_64 only. The download page lists CLI 1.4.0 for macOS Apple Silicon and Intel, Linux x86_64 and arm64, and Windows x86_64. Image scans use syft and grype, which the interactive terminal downloads the first time it runs.

First session

cd ~/projects/storefront
safeguard

With no arguments, safeguard opens the interactive terminal. Type /login to sign in through the browser. /scan takes a folder, image or repo and asks for a target if you give none. Scanning from the interactive terminal while signed in uploads the SBOM and findings, never your source. You can also just ask: "how many critical vulns do we have".

Writing and fixing code

The Safeguard CLI writes code, not only scans it. The assistant has tools for your local files and for your Safeguard data, and picks whichever the question needs. Ask it to update the base image in your Dockerfile and it can edit the file, run the rebuild and run the scan again, with your approval at each step that changes something.

This walkthrough shows the same loop from the platform: attach a computer running the Safeguard CLI and a folder, ask for a fix, review the diff and approve the command before it runs. The demo itself notes that computer work from Search is available and expanding.

Click through it at your own pace, or open it full screen.

Keep your Claude, ChatGPT or Codex plan

Bring the Claude, ChatGPT or Codex plan you already pay for and use it inside the Safeguard CLI. Safeguard adds no extra charge for that usage; your plan's own usage limits and terms still apply. /model shows or switches the model, and the CLI docs cover connecting one. @ attaches a file from the folder you started in, which is handy for @Dockerfile, and /resume picks up the last session in that folder.

Approvals

Tools that write a file, edit code or run a command ask before they act. /permissions sets the mode: Ask prompts before every action, Auto approves known actions and asks for anything new (the default), and Yolo approves everything. A rebuild and a rescan are commands, so in Ask or Auto you see each one before it runs.

CI with safeguard exec

For pipelines, safeguard exec "summarise the critical findings in this repository" runs one non-interactive assistant turn. Its default permission mode is strict, so nothing is asked and anything the policy file has not pre-approved is denied. Pair it with safeguard scan --image after your build step.

One platform on every surface

The download page lists the rest: a desktop app (3.2.2) for Windows, macOS and Linux, iOS and Android apps, IDE extensions, a browser extension, and cloud options with nothing to install, including the Cloud CLI and a cloud sandbox tenant against a seeded demo repo, free for seven days with no card. Scan the image in the terminal, review the product on the desktop, and check the result on your phone.

Step by step: scan an image from the terminal

These are the image commands from the Safeguard docs, in the order you are likely to need them.

1. A public image

safeguard scan --image nginx:latest

If the CLI cannot find the image, the troubleshooting guide suggests pulling it first or using the full reference:

docker pull nginx:latest
safeguard scan --image docker.io/library/nginx:latest

2. A private registry

safeguard scan --image myregistry.azurecr.io/myapp:v1 \
  --registry-username "$REGISTRY_USER" \
  --registry-password "$REGISTRY_PASSWORD"

Keep the credentials in environment variables, as here, so they do not end up in your shell history. If authentication fails, the troubleshooting guide says to log in to the registry with Docker first, then pass the credentials to the scan.

3. Images that never left your machine

--image also reads local images in these forms:

SourceFormWhen to use it
Registry or local tagnginx:latest, docker.io/library/nginx:latestPublic or private images by reference
Podman storepodman: followed by the image referenceImages built with Podman
Docker archivedocker-archive: followed by a fileA docker save tarball, handy in CI
OCI archiveoci-archive: followed by a fileAn OCI image packed as a single file
OCI layout folderoci-dir: followed by a folderAn OCI image laid out on disk
Singularity or Apptainera path to a .sif fileHPC and research images

A build that never pushes is a good fit for the archive form:

docker build -t myapp:candidate .
docker save myapp:candidate -o myapp-candidate.tar
safeguard scan --image docker-archive:myapp-candidate.tar --output out/candidate

4. Every image in a Podman store

safeguard podman scan --all --json --keep ./scans

safeguard podman lists and scans the images in a Podman store through the Podman REST API. No Docker and no inbound connection are needed, and results stay on the machine.

5. SIF images

safeguard scan --image image.sif

For more detail, safeguard sif (also available as safeguard apptainer and safeguard singularity) inspects, verifies and scans a SIF image, and its scan writes sif-provenance.json next to the usual output.

6. Check the exit code

safeguard scan returns 0 when the scan ran, whatever it found, 1 when the command failed, and 2 when results were written but the vulnerability scan did not run. There is no severity threshold flag; decide on severity by reading vuln.json, or let platform policies and gates decide.

7. The same scan in a pipeline

The commands above work unchanged in CI. Build, save, scan, check and keep the results. This GitHub Actions job assumes syft, grype, jq and the Safeguard CLI are already installed on the runner (the CLI looks for scanners in TOOLS_DIR, next to its binary, then on PATH):

      - name: Build and save the image
        run: |
          docker build --pull -t storefront:ci .
          docker save storefront:ci -o storefront-ci.tar

      - name: Scan the image
        run: safeguard scan --image docker-archive:storefront-ci.tar --output out/image

      - name: Fail on critical
        run: |
          jq -e '[.data.matches[].vulnerability.severity | ascii_downcase] | index("critical") | not' out/image/*/vuln.json

      - name: Keep results
        if: always()
        uses: actions/upload-artifact@v7
        with:
          name: image-scan
          path: out/image/

The jq gate is the one the Safeguard CI docs show for vuln.json. Add a scheduled trigger that scans the image you have in production, too: a deployed image can collect new findings without a single code change.

Reading the results

Each scan writes a folder under the output directory (default output) containing the SBOM plus vuln.json, license.json and collection.json, among others. vuln.json is grype compatible, with matches under .data.matches. Each match pairs a vulnerability (an id, a severity, and a fix object with versions and state) with the artifact it was found in. Look at one real match before you script against the fields:

jq '.data.matches[0]' out/candidate/*/vuln.json

Count by severity

jq '[.data.matches[].vulnerability.severity | ascii_downcase]
    | group_by(.) | map({(.[0]): length}) | add' out/candidate/*/vuln.json

Group findings by package

Base image problems tend to cluster in a handful of OS packages. Grouping by package name shows that quickly:

jq -r '[.data.matches[] | .artifact.name]
  | group_by(.) | map({n: length, name: .[0]}) | sort_by(.n) | reverse
  | .[] | "\(.n)\t\(.name)"' out/candidate/*/vuln.json | head -20

If the artifact in your output carries a type field (for example the package ecosystem), swap .artifact.name for .artifact.type to see how many findings come from OS packages versus application dependencies.

Separate what you can fix from what you cannot

Grype records a fix state of fixed, not-fixed, wont-fix or unknown. This splits the work:

jq -r '.data.matches[]
  | [.vulnerability.fix.state, .vulnerability.severity, .vulnerability.id, .artifact.name, .artifact.version]
  | @tsv' out/candidate/*/vuln.json | sort | uniq

A fixed finding has a newer package version upstream, which usually means a newer base image tag or a rebuild with --pull clears it. A not-fixed finding has no upstream fix yet, so your options are a different base image, removing the package, or recording a decision. That decision belongs in the platform, where policies and gates are evaluated.

Compare before and after

for d in out/current out/candidate; do
  printf '%s ' "$d"
  jq -c '[.data.matches[].vulnerability.severity | ascii_downcase]
         | group_by(.) | map({(.[0]): length}) | add' "$d"/*/vuln.json
done

One line per image, side by side, is the evidence a reviewer wants on the pull request that changes a base image.

Fixing base images with the assistant

Here is a full session. Start in the folder that holds the Dockerfile.

cd ~/projects/storefront
safeguard

Scan what you run today. Type /scan, give it the image when it asks, or scan from the shell into a named folder so you can compare later:

safeguard scan --image myregistry.azurecr.io/storefront:current --output out/current

Then ask, attaching the Dockerfile with @:

@Dockerfile The last image scan shows critical findings in OS packages from
the base image. Suggest a newer or smaller base image for this app, explain
the trade-offs, and update the Dockerfile once I approve.

Read the proposal before you approve the edit. Then ask for the rebuild and rescan:

Rebuild the image as storefront:candidate with docker build --pull, save it
with docker save, scan it with safeguard scan --image docker-archive:... into
out/candidate, and compare critical and high counts with out/current.

Each of those is a command, so it asks before it runs. If the candidate looks better, run your tests, commit, and let CI scan it again. If it does not, ask a follow-up in the same session; the context carries over, and /resume brings it back tomorrow.

Base image options, side by side

The assistant will usually propose one of these directions. The facts come from Docker's documentation and the distroless project.

ApproachWhat is in the final imageShellNotes
Newer tag of the same baseSame distribution, newer packagesYesSmallest change; clears fixed findings when the new tag carries the patched packages
AlpineA small, tightly controlled distribution, under 6 MB per Docker's guideYesDocker suggests it as a small base; test that your app runs on it
Distroless (Apache-2.0)Your application and its runtime dependencies onlyNo, except :debug tags, which add a busybox shellNo package manager; ENTRYPOINT and CMD must use the JSON array form
scratch in a multi-stage buildOnly what you copy in, such as a static binaryNoDocker's example ends with "nothing but the binary inside"

A multi-stage build to a distroless base

The pattern from Docker's multi-stage guide and the distroless README: build with the full toolchain, then copy only the result into a minimal image.

# syntax=docker/dockerfile:1
FROM golang:1.26 AS build
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o /app ./cmd/app

FROM gcr.io/distroless/static-debian13:nonroot
COPY --from=build /app /app
CMD ["/app"]

None of the build tools reach the final image, which is the point. Distroless images come in latest, nonroot, debug and debug-nonroot tags; use a debug tag only while you investigate, since it adds a shell.

Pin the base, then keep it moving

Pinning to a digest guarantees the same base every time, even if the publisher replaces the tag. Docker's guide shows the form:

FROM alpine:3.21@sha256:a8560b36e8b8210634f77d9f7f9efd7ffa463e380b75e2e74aff4511df3ef88c

The trade-off, as Docker notes, is that you opt out of automatic security fixes and must update digests yourself. Docker suggests Dependabot with the docker package ecosystem to open pull requests that bump tags and digests on a schedule:

version: 2
updates:
  - package-ecosystem: "docker"
    directory: "/"
    schedule:
      interval: "weekly"

Scan each of those pull requests with safeguard scan --image before you merge, and the pin stops being a liability. For a broader review habit, see checking coding agent changes with the Safeguard CLI.

Prefer to ask from a chat app? This walkthrough connects ChatGPT to the Safeguard MCP server with OAuth and lets you choose which tools ChatGPT can use, so you can ask about image findings from the chat you already use.

Click through it at your own pace, or open it full screen.

Common mistakes

Scanning a tag instead of what you ship. Tags move. Scan the archive or reference you actually deploy, and pin the base when you need a repeatable build.

Rebuilding without --pull. A cached base image stays old. --no-cache alone does not fetch a fresh base either, per Docker's guide; combine --pull --no-cache for a full refresh.

Treating exit code 0 as clean. It means the scan ran. Read vuln.json.

Ignoring exit code 2. Results were written but the vulnerability scan did not run. Run safeguard doctor, which checks environment, sign-in, tools and policy, and safeguard update to refresh the vulnerability databases.

Passing registry passwords inline. Use environment variables or your CI secret store.

Chasing not-fixed findings with upgrades. There is no fixed version yet. Change the base, remove the package, or record the decision in the platform.

Debugging in production with a debug tag. It adds a shell. Use it locally, then switch back.

FAQ

Can I keep my Claude, ChatGPT or Codex plan when I use Safeguard?

Yes. Bring the Claude, ChatGPT or Codex plan you already pay for and use it inside the Safeguard CLI. Safeguard adds no extra charge for that usage; your plan's own usage limits and terms still apply. You can also connect Claude or ChatGPT to the Safeguard MCP server and use Safeguard from the chat you already use. See the CLI docs or /model for connecting a model.

Do I need Docker installed to scan an image?

Not for every source. safeguard podman works through the Podman REST API with no Docker needed, and OCI archives, OCI folders and SIF files are files on disk. You need Docker for docker build, docker save and docker login.

Does my image or source code get uploaded?

Scanning from the interactive terminal while signed in uploads the SBOM and findings, never your source. When the local runner runs platform scan jobs, only SBOMs and findings are uploaded.

Can I fail a build on image findings?

safeguard scan has no severity flag, so read vuln.json with jq in your pipeline, or let platform policies and gates make the call. Policies and gates are evaluated in the platform, not by the CLI.

Can the assistant change my Dockerfile?

Yes, with your approval. Editing a file and running a command both ask before they act, according to your /permissions mode.

How often should I rescan an image?

On every build, and on a schedule for images already deployed. New advisories are published against packages that have not changed, so last month's clean scan says little about today. Docker's guide also recommends rebuilding regularly with --pull so the base picks up patches.

Which image formats are supported?

Registry and local references, podman:, docker-archive:, oci-archive: and oci-dir: sources, and SIF files. For the assistant side, see using the Safeguard CLI as an AI assistant on your machine and the security aware coding assistant post.

Try it yourself

This walkthrough sets up the runner on your own machine, picks the platform, starts it, approves it with its device code, then uses the same CLI to scan from the terminal.

Click through it at your own pace, or open it full screen.

Then download the Safeguard CLI, run safeguard scan --image on an image you ship, and ask the assistant what to do about the base. The full reference is in the CLI docs.

Quick check

Which flags scan an image in a private registry?

--image with the full reference, plus --registry-username and --registry-password.

What does a not-fixed finding mean for a base image?

There is no upstream fixed version yet, so upgrading will not clear it. Change the base, remove the package, or record a decision in the platform.

Sources

Last reviewed: 21 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.