Scan Django Docker Images for Vulnerabilities With Trivy
Scan Django container images with Trivy for vulnerable OS packages and Python dependencies: run it locally and in CI, fail builds on fixable critical issues, ignore false positives with expiry, rebuild on new base images, and scan running registries.
Problem statement
A Django image contains far more than your code: a Debian or Alpine userland with OpenSSL, libxml2, and image libraries, plus every Python package from your lock file. New vulnerabilities are published daily for those components. An image built six months ago and never rebuilt carries six months of known CVEs. Trivy, an open-source scanner, finds vulnerable OS packages and Python dependencies in images, filesystems, and lock files, and fits into CI so known-vulnerable images do not ship and old ones get rebuilt.
- Run
trivy image --severity HIGH,CRITICAL --ignore-unfixed myapp:latestlocally to see the current state. - Add Trivy to CI after the image build; fail the job on fixable CRITICAL (and optionally HIGH) findings.
- Scan the lock file early with
trivy fs --scanners vuln .to catch Python issues before building. - Record accepted risks in
.trivyignorewith a reason and review date. - Rebuild images at least weekly from the latest base image, even without code changes.
- Use slim base images and multi-stage builds to reduce the number of packages to patch.
Step-by-step solution
1. Scan locally
trivy image --severity HIGH,CRITICAL --ignore-unfixed ghcr.io/you/myapp:3f2a1c9
The report groups findings by target: the OS packages (from debian:bookworm-slim or similar) and Python packages found in site-packages. --ignore-unfixed hides vulnerabilities with no available fix, which you cannot act on yet.
2. Fail CI on fixable critical issues
# GitHub Actions
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
- name: Trivy scan
uses: aquasecurity/trivy-action@0.28.0
with:
image-ref: myapp:${{ github.sha }}
severity: CRITICAL,HIGH
ignore-unfixed: true
exit-code: "1"
format: table
Start with exit-code: 0 and SARIF upload to GitHub's security tab to see the baseline, then enforce. Failing on every HIGH from day one usually blocks deploys over issues that need a base image update rather than code changes.
3. Scan dependencies before building
trivy fs --scanners vuln --severity HIGH,CRITICAL .
This reads requirements.txt, poetry.lock, or uv.lock and flags vulnerable Python packages in pull requests, before an image exists. It complements tools like pip-audit and Dependabot alerts.
4. Manage exceptions
# .trivyignore
# CVE in libtiff via Pillow wheel; we do not accept TIFF uploads. Review 2026-12-01.
CVE-2025-12345
Use .trivyignore.yaml to add expiry dates so exceptions resurface. Every ignore needs a reason a reviewer can check.
5. Rebuild regularly
Base images receive security updates continuously. Schedule a weekly CI job that rebuilds with --pull --no-cache (or at least --pull), scans, runs tests, and deploys if green. Pin base images by digest for reproducibility and let Renovate or Dependabot propose digest updates.
6. Shrink the attack surface
Multi-stage builds keep compilers and headers out of the runtime image; python:3.12-slim has far fewer packages than the full image; distroless images go further. Fewer packages mean fewer findings and faster scans.
Verification check: the CI job shows a Trivy report for each build, a deliberately old base image fails the gate, and the weekly rebuild job produces images with fewer or no fixable critical findings.
Explanation
Trivy identifies the packages inside an image by reading OS package databases and Python package metadata, then matches name and version against vulnerability databases from distributions and advisories (including GitHub Security Advisories for Python). Because most vulnerabilities in a Django image live in the base OS and third-party packages rather than your code, the effective remedies are updating base images and dependencies. Integrating the scan into CI turns that into routine work with a clear policy, instead of an occasional audit.
Edge cases / notes
- False positives happen when distribution backports fix a CVE without changing the upstream version; Trivy uses distro advisories to reduce these.
- Alpine vs Debian. Alpine has fewer packages but uses musl; scanner coverage is good for both.
- Registry scanning. GHCR, ECR, and Artifact Registry offer built-in scanning; Trivy can also scan registry images on a schedule.
- SBOMs.
trivy image --format cyclonedxproduces a software bill of materials for compliance.
Next steps
- Review supply-chain hygiene with the Django Dependency Security Checklist.
- Keep dependencies current with Automate Django Dependency Updates.
FAQ
What does Trivy scan in a Django image?
OS packages from the base image and Python packages installed in the image, plus misconfigurations and secrets if enabled.
Should CI fail on every vulnerability?
Start by failing on fixable criticals, then tighten as the baseline improves.
How do I fix vulnerabilities in the base image?
Rebuild from the latest version of the base image; many fixes arrive without code changes.
How do I ignore a false positive?
Add it to .trivyignore or .trivyignore.yaml with a reason and an expiry date.