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:
| Source | Form | When to use it |
|---|---|---|
| Registry or local tag | nginx:latest, docker.io/library/nginx:latest | Public or private images by reference |
| Podman store | podman: followed by the image reference | Images built with Podman |
| Docker archive | docker-archive: followed by a file | A docker save tarball, handy in CI |
| OCI archive | oci-archive: followed by a file | An OCI image packed as a single file |
| OCI layout folder | oci-dir: followed by a folder | An OCI image laid out on disk |
| Singularity or Apptainer | a path to a .sif file | HPC 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.
| Approach | What is in the final image | Shell | Notes |
|---|---|---|---|
| Newer tag of the same base | Same distribution, newer packages | Yes | Smallest change; clears fixed findings when the new tag carries the patched packages |
| Alpine | A small, tightly controlled distribution, under 6 MB per Docker's guide | Yes | Docker suggests it as a small base; test that your app runs on it |
| Distroless (Apache-2.0) | Your application and its runtime dependencies only | No, except :debug tags, which add a busybox shell | No package manager; ENTRYPOINT and CMD must use the JSON array form |
scratch in a multi-stage build | Only what you copy in, such as a static binary | No | Docker'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
- Safeguard CLI overview
- Safeguard CLI usage guide
- Safeguard CLI troubleshooting
- Safeguard CLI interactive terminal
- Safeguard CI/CD integration
- Safeguard downloads
- Docker build best practices
- Docker multi-stage builds
- Distroless repository
- Distroless licence
- Grype repository
- Syft repository
- Grype JSON output guide
- actions/upload-artifact
Last reviewed: 21 October 2026.