Guide
Matt S
Matt S
Platform engineer at Fortem··10 min read
ecr-image-scanningecr-basic-vs-enhancedamazon-inspector-ecr

How Do You Scan ECR Images for Vulnerabilities Before They Deploy?

A compliance review says "scan container images for vulnerabilities before they run." You enable ECR scan-on-push, get a wall of findings, and hit the real questions: basic or enhanced, what each actually catches, what Amazon Inspector costs across your repos, and how to actually block a bad image. This is the decision and the wiring — not a feature list.

TL;DR
  • ·Basic scanning is free but scans OS packages ONLY — your app's npm, pip, and gem CVEs are invisible to it.
  • ·Enhanced scanning (Amazon Inspector) adds language-package CVEs plus continuous re-scans, billed per image: $0.09 initial, $0.01 per re-scan.
  • ·Scanning finds vulnerabilities; it never stops the deploy. You gate the pipeline on CRITICAL/HIGH yourself.
  • ·At fleet scale the hard part isn't scanning — it's knowing which vulnerable image is actually running. Inspector maps images to running Fargate tasks.
  • ·AWS-native scanning is enough for most ECS teams. There's a clear line where it isn't.
Ready to use — fail the build on a CRITICAL or HIGH finding
bash
# In your CI pipeline, after pushing the image. Basic or enhanced — same API.
REPO=my-service
TAG=$GIT_SHA

# Wait for the scan to finish, then read the severity counts
aws ecr wait image-scan-complete --repository-name "$REPO" --image-id imageTag="$TAG"

COUNTS=$(aws ecr describe-image-scan-findings \
  --repository-name "$REPO" --image-id imageTag="$TAG" \
  --query 'imageScanFindings.findingSeverityCounts' --output json)

CRIT=$(echo "$COUNTS" | jq -r '.CRITICAL // 0')
HIGH=$(echo "$COUNTS" | jq -r '.HIGH // 0')

if [ "$CRIT" -gt 0 ] || [ "$HIGH" -gt 0 ]; then
  echo "Blocking deploy: $CRIT CRITICAL, $HIGH HIGH findings"
  exit 1
fi
echo "Scan clean — proceeding."

This is the gate ECR doesn't ship. Nothing stops a vulnerable image from deploying until you make it.

Basic vs enhanced — the decision that actually matters

Basic scanning is free but scans OS packages only. Enhanced scanning (Amazon Inspector) adds programming-language package CVEs and continuous re-scans — billed per image, not free.

DimensionBasicEnhanced (Inspector)
ScansOS packages onlyOS + language packages
CVE sourceAWS-native databaseAmazon Inspector
FrequencyOn push / manualOn push / continuous
Re-scan on new CVENoYes
CostFree$0.09 init + $0.01 re-scan / image
Findings mgmtECR findings APIEventBridge + Security Hub
Source: AWS ECR + Inspector docs and Inspector pricing, verified July 2026.

Every other setting is a footnote next to this one choice. Basic scanning is genuinely free and turns on with a checkbox, which makes it tempting to stop there. But it only looks at operating-system packages — the Debian, Alpine, or Amazon Linux layers of your image. Enhanced scanning hands the job to Amazon Inspector, which adds the layer that actually breaks most teams: your application's language dependencies. Scanning is the supply-chain half of the broader posture in what you actually configure for ECS Fargate container security — a clean scan means nothing if the runtime is wide open, and a hardened runtime means nothing if the image ships a known CVE.

What basic scanning misses (and why it matters)

Basic scanning never looks at your application dependencies. A CRITICAL CVE in an npm or pip package ships clean through basic — only enhanced/Inspector language scanning catches it.

Picture the failure. Your Node service bundles a version of a logging library with a known remote-code-execution CVE. You have basic scanning on, scan-on-push is green, the pipeline is happy — and that CVE is sitting in production, because basic scanning never opened your node_modules. It scanned the OS layer, found nothing there, and reported clean. The vulnerability was real; the scanner just wasn't looking where it lived.

This is exactly the re:Post question people hit — the same image returns different findings under basic versus enhanced, and the reason is scope, not a bug. Basic covers OS packages; enhanced covers OS plus npm, pip, gem, Maven, and the rest. For anything you're deploying to a regulated environment, the language-package layer is not optional — that's where most of your exploitable surface actually is.

Key insight

"We have ECR scanning enabled" is not the same statement as "we scan our application dependencies." If it's basic, you scan the base image and nothing you actually wrote or imported. An auditor who knows the difference will ask which one you run — have the answer be enhanced.

What enhanced scanning costs across a fleet

Inspector bills $0.09 per initial image scan and $0.01 per re-scan. Across dozens of repos pushing daily, the re-scan line is the ongoing cost — model it before you enable it registry-wide.

The sticker price looks trivial, and per image it is. The initial scan of a freshly pushed image is $0.09; each re-scan is $0.01. New accounts get a 15-day free trial of continuous ECR scanning, so you can measure your real volume before you pay for anything. The number that actually grows is re-scans, because continuous scanning re-scans your images every time Inspector adds a relevant new CVE — not on your schedule, on the CVE feed's. A fleet with a few hundred active images across many repos will see a steady trickle of $0.01 re-scans as the vulnerability database updates.

The lever that controls the bill is the same one that controls your storage bill: how many images you keep around. A repo with a lifecycle policy that prunes old images scans a handful of live tags; a repo with five years of untagged layers scans everything. The lifecycle policy that keeps ECR storage costs down keeps your scan bill down too — fewer images means fewer scans.

Turning it on: scan-on-push and continuous

Basic offers scan-on-push or manual; enhanced adds continuous. Scan-on-push catches new images; continuous re-scans them when a new CVE lands — so yesterday's clean image gets flagged today.

The distinction that matters operationally is scan-on-push versus continuous. Scan-on-push runs once, when the image arrives — it tells you the image was clean at the moment you built it. Continuous scanning keeps watching: when a new CVE is published that affects a package already in your image, Inspector re-scans it and raises a finding, even though nothing about the image changed. That's the whole point of continuous — the image that passed last week is not guaranteed to pass today, because a new CVE was published against a package it already ships.

Enhanced scanning is configured at the registry level, and by default Inspector monitors images pushed or in use within the last 14 days. Set the re-scan duration to match how long your images actually stay in service — a long-lived base image needs a longer window than a feature-branch tag that lives for an afternoon.

Scanning doesn't block the deploy — you do

ECR scanning finds vulnerabilities; it never stops a deploy on its own. Gate the pipeline yourself: query the scan findings and fail the build when the CRITICAL or HIGH count is above zero.

This is the part people assume is automatic and it isn't. Enabling scanning gives you findings, a dashboard, and EventBridge events — it does not put a gate in front of your deploy. A CRITICAL-riddled image will roll straight to production unless you wrote the step that stops it. The Ready-to-use block above is that step: after the push, wait for the scan, read findingSeverityCounts, and exit non-zero when CRITICAL or HIGH is above zero. Any HIGH you've deliberately accepted should be suppressed in Inspector (see triage below) so it drops out of the count rather than blocking the build.

For enhanced scanning you have a second, event-driven option: Inspector emits an EventBridge event on new findings, so you can trigger a Lambda that blocks a deployment, opens a ticket, or pages the owner — useful for the continuous case, where a new CVE lands on an image that already shipped. Pick the threshold deliberately. Gating on CRITICAL only is permissive; gating on MEDIUM and up will halt every build on day one. Most teams gate on CRITICAL + HIGH and triage the rest.

Triaging findings without alert fatigue

A registry-wide scan surfaces thousands of findings, most unactionable. Gate builds on CRITICAL/HIGH only, suppress accepted-risk CVEs, and route Inspector's EventBridge events to one owned channel.

Turn on enhanced scanning across a real registry and the first result is thousands of findings, which is worse than useless if you treat every line as urgent. The team stops reading the alerts within a week. The fix is ruthless prioritization. Gate the pipeline on CRITICAL and HIGH; everything below that is a backlog, not an alarm. Suppress CVEs you've formally accepted the risk on — a finding in a package you don't call, or one with no upstream fix, should be recorded and silenced, not re-raised daily.

Route the events to one place. Inspector's findings flow to Security Hub and EventBridge; pointing them at a single security channel with an owner beats fanning raw findings into every team's inbox. The goal is that a CRITICAL finding is a thing one person sees and acts on, not a thing everyone ignores.

Which vulnerable image is actually running?

A CVE in a repo doesn't matter if nothing runs it. Inspector maps ECR images to running Fargate tasks (ecrImageInUseCount), so you patch the image that's actually deployed, not one rotting in a repo.

Severity alone is the wrong prioritizer, because a CRITICAL in an image nothing runs is lower risk than a HIGH in the image serving all your prod traffic. Inspector closes that gap by mapping images to running containers across ECS and EKS — it exposes ecrImageInUseCount and ecrImageLastInUseAt so you can filter findings to images that are actually deployed right now. This works on Fargate. Patch the running image first.

This is also where single-account scanning breaks down. Inspector shows you the mapping within an account, but a fleet spread across prod, staging, and per-customer accounts means the same vulnerable image can be running in five places you have to check five times. Knowing, in one view, which running tasks across every account carry an unpatched CRITICAL is the part AWS-native tooling makes you assemble yourself.

When AWS-native scanning is enough — and when it isn't

For most ECS teams, enhanced Inspector scanning plus a CRITICAL/HIGH build gate is enough. Reach for a third party when you need registry-agnostic scanning, runtime correlation, or a multi-cloud pane.

It's worth being honest here, because the vendor guides aren't. If your images live in ECR, your workloads run on ECS or EKS, and you're on AWS, enhanced Inspector scanning with a build-time gate covers the vast majority of what a container-scanning program needs to do. You don't need a third-party scanner to be compliant or safe. The line is real but specific.

Key insight

You reach for a third party (Wiz, Sysdig, Trend, Snyk) when one of three things is true: your images live in registries other than ECR and you want one scanner for all of them; you need scanning tied to runtime behavior, not just the image; or you're multi-cloud and want a single pane across AWS, GCP, and Azure. If none of those hold, AWS-native is the cheaper, simpler answer — and "simpler" is a security property.

If you read this, you might also want to know

Does scanning slow down my deploy?

Scanning runs asynchronously — pushing the image doesn't wait on it. Your deploy only slows down if you add a gate that waits for the scan to complete before promoting, which is the point. A scan on a normal image completes in seconds to a couple of minutes; budget for it in the pipeline, don't skip the wait.

How do I suppress a CVE I've accepted the risk on?

With enhanced scanning, use Amazon Inspector suppression rules — filter by CVE ID, package, or severity and the matching findings stop being raised while staying recorded for audit. Don't suppress in your head or in a wiki; suppress in the tool, so the acceptance is auditable and the finding doesn't re-alarm every re-scan.

Do I still need scanning if my base image is from a trusted registry?

Yes. A trusted base image was clean when it was published, not necessarily today — new CVEs land against old packages constantly. And your application layer on top (your language dependencies) is never covered by the base image's provenance. Scan the final image you actually ship, every time.

Frequently asked questions

Which running tasks carry an unpatched CVE?

Inspector maps images to tasks inside one account. Across prod, staging, and per-customer accounts, nobody can say in one view which running ECS tasks are on a vulnerable image. Fortem shows that across your whole fleet. Book a 20-minute call and we'll walk yours.

Book a 20-min call
Worth reading