Key Takeaways
- Amazon ECR offers two scanning tiers: basic scanning (OS package CVEs via Clair, on push) and enhanced scanning (continuous rescanning plus OS and programming-language package vulnerabilities, powered by Amazon Inspector).
docker container security scanningshould run at three points: locally before commit, in CI before merge, and continuously in the registry after push — no single point catches everything.- Enhanced scanning re-evaluates images automatically whenever a new CVE is published, not just at push time — a critical difference from basic scanning.
- Findings can be routed to EventBridge and automatically fail a deployment pipeline when a CRITICAL or HIGH severity finding appears above a defined threshold.
- Scanning catches known CVEs; it doesn't replace minimal base images, non-root users, and signed images — all layers of the same defense.
A docker image that passed a security review six months ago is not secure today — new CVEs get published against the same OS packages and language dependencies baked into that image every week. docker security scanning on AWS means treating vulnerability scanning as a continuous control, not a one-time gate, and that's exactly the architecture ECR enhanced scanning and Amazon Inspector are built for.
ECR Basic Scanning vs. Enhanced Scanning
| Feature | Basic scanning | Enhanced scanning |
|---|---|---|
| Engine | Clair (open source) | Amazon Inspector |
| Coverage | OS packages only | OS packages + language packages (npm, pip, gems, Maven, Go modules) |
| Trigger | On push only | On push, plus continuous rescans on new CVE publication |
| Findings location | ECR console | ECR console + Inspector console + EventBridge |
| Cost | Free | Priced per image scanned |
Enabling enhanced scanning at the registry level:
aws ecr put-registry-scanning-configuration \
--scan-type ENHANCED \
--rules '[
{
"scanFrequency": "CONTINUOUS_SCAN",
"repositoryFilters": [{ "filter": "*", "filterType": "WILDCARD" }]
}
]'
This turns on continuous scanning for every repository in the registry. For most production environments, continuous scanning on all repos is worth the extra cost — a CVE published against a package in an image you pushed three months ago still needs to surface.
Scanning in the CI/CD Pipeline, Before Push
Waiting until an image lands in ECR to find vulnerabilities means they've already been built, tested, and are one step from deploy. Scan earlier with an open-source scanner like Trivy inside your build job:
# .github/workflows/build.yml (excerpt)
- name: Build image
run: docker build -t app:${{ github.sha }} .
- name: Scan image with Trivy
uses: aquasecurity/trivy-action@master
with:
image-ref: "app:${{ github.sha }}"
severity: "CRITICAL,HIGH"
exit-code: "1" # fail the job if findings exist
ignore-unfixed: true # don't block on CVEs with no available fix
exit-code: "1" is the actual gate — without it, Trivy reports findings but the pipeline continues regardless.
Reacting to ECR Findings Automatically
Enhanced scanning publishes findings to EventBridge, so you can react without polling the console:
{
"source": ["aws.inspector2"],
"detail-type": ["Inspector2 Finding"],
"detail": {
"severity": ["CRITICAL", "HIGH"]
}
}
Route that rule to a Lambda function that checks whether the affected image tag is deployed to production, and if so, notifies the on-call channel or automatically rolls back:
import boto3
ecs = boto3.client("ecs")
def lambda_handler(event, context):
finding = event["detail"]
image_uri = finding["resources"][0]["id"]
severity = finding["severity"]
if severity in ("CRITICAL", "HIGH"):
# Check if any running ECS service uses this image tag
services = ecs.list_services(cluster="production")["serviceArns"]
for service_arn in services:
task_def = ecs.describe_services(
cluster="production", services=[service_arn]
)["services"][0]["taskDefinition"]
if image_uri in str(ecs.describe_task_definition(taskDefinition=task_def)):
print(f"ALERT: {severity} finding affects running service {service_arn}")
return {"status": "processed"}
Beyond Scanning: Reducing What There Is to Find
Scanning tells you what's wrong; it doesn't fix the habits that create findings in the first place.
- Use minimal base images —
distrolessoralpineimages ship far fewer OS packages thanubuntu:latest, meaning far fewer CVEs to scan for in the first place. - Run as non-root — add
USER appuserin your Dockerfile so a container compromise doesn't hand over root inside the container. - Pin versions —
FROM node:20.11.1-alpineinstead ofFROM node:latestso you know exactly what you're scanning and can reproduce builds. - Rebuild regularly — even with no code changes, rebuild base images weekly so OS package patches land before a scanner flags them as outstanding.
FROM node:20.11.1-alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
COPY --chown=appuser:appgroup . .
RUN npm ci --omit=dev
USER appuser
CMD ["node", "server.js"]
Scanning Coverage Across the Pipeline
<svg viewBox="0 0 640 180" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Docker image scanning stages from local build to runtime">
<style>
.stage { fill: none; stroke: currentColor; stroke-width: 1.5; }
.lbl { fill: currentColor; font-family: sans-serif; font-size: 12px; }
.arrow { stroke: currentColor; stroke-width: 1.5; marker-end: url(#a2); }
</style>
<defs>
<marker id="a2" markerWidth="8" markerHeight="8" refX="6" refY="4" orient="auto">
<path d="M0,0 L8,4 L0,8 Z" fill="currentColor"/>
</marker>
</defs>
<rect class="stage" x="10" y="60" width="120" height="50"/>
<text class="lbl" x="22" y="90">Local build</text>
<rect class="stage" x="175" y="60" width="120" height="50"/>
<text class="lbl" x="187" y="82">CI (Trivy)</text>
<text class="lbl" x="187" y="98">pre-merge</text>
<rect class="stage" x="340" y="60" width="120" height="50"/>
<text class="lbl" x="352" y="82">ECR Enhanced</text>
<text class="lbl" x="352" y="98">on push</text>
<rect class="stage" x="505" y="60" width="120" height="50"/>
<text class="lbl" x="517" y="82">Continuous</text>
<text class="lbl" x="517" y="98">rescans</text>
<path class="arrow" d="M130,85 L175,85"/>
<path class="arrow" d="M295,85 L340,85"/>
<path class="arrow" d="M460,85 L505,85"/>
</svg>
FAQ
Does ECR basic scanning catch vulnerabilities in application dependencies like npm packages? No. Basic scanning (Clair) only covers OS-level packages. Enhanced scanning, powered by Amazon Inspector, adds coverage for npm, pip, Maven, Go modules, and other language ecosystem packages.
Will enhanced scanning re-scan an image I pushed last month if a new CVE comes out today? Yes — that's the core difference from basic scanning. Continuous scan mode automatically re-evaluates stored images against newly published CVEs without requiring a new push.
Should scanning happen in CI, in the registry, or both? Both, plus locally if possible. CI scanning (e.g., Trivy) blocks a build before merge; registry scanning (ECR) catches drift from new CVEs published after the image already shipped.
What should happen when a CRITICAL vulnerability is found in a running production image? Route the Inspector/EventBridge finding to an automated check against your running task definitions or pod specs, and alert or trigger a rollback if the affected image is live — don't rely on someone checking the console.
Build a Continuous Scanning Pipeline
f9infotech helps teams wire ECR enhanced scanning, CI gates, and EventBridge-driven alerting into one pipeline so vulnerable images get caught before — not after — they reach production. Talk to our DevSecOps team about a container security assessment.