GitHub Actions has become the default CI/CD platform for a large fraction of software development. If your containers are built in GitHub Actions and your security scanning is not integrated with that workflow, you have a gap: images can be built and deployed without passing through a security gate.
Integrating container scanning into GitHub Actions workflows is well-documented for basic cases. The practical challenges — what to do when the scanner finds something, how to avoid blocking all builds, how to prevent the security step from becoming a developer frustration point — require more thought than the integration guide covers.
The Basic Integration: What Works and What Breaks
A minimal GitHub Actions container scanning workflow:
– name: Build image
run: docker build -t myapp:${{ github.sha }} .
– name: Scan image
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}
exit-code: 1
severity: CRITICAL,HIGH
This works in the sense that it scans the image and fails the build if Critical or High CVEs are found. It fails in practice because:
It blocks builds constantly: Most base images have High CVEs. The first time you add this step to an existing CI workflow, it fails. The second time you push, it fails again. The developer marks it as a known issue and asks how to bypass it.
There is no fix path: The build failed because of CVEs. What should the developer do? Update the base image? Which CVE? The scanner output points to problems without pointing to solutions.
The finding volume is unmanageable: 300 findings from a single image scan in a GitHub Actions log is not something developers can triage during a code review.
The integration that works addresses each of these failure modes.
Adding Hardening Before Scanning
The pattern that makes scanning actionable:
– name: Build image
run: docker build -t myapp:${{ github.sha }} .
– name: Harden image
# Profile the image against the test suite and remove unused components
# This step produces a hardened image with 70-90% fewer packages
run: |
# Run hardening tooling – specific steps depend on your hardening solution
# Output: myapp:${{ github.sha }}-hardened
– name: Scan hardened image
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}-hardened
exit-code: 1
severity: CRITICAL
# Only block on Critical – we have addressed the High CVEs via hardening
By scanning the hardened image, the scanner evaluates only the packages the application actually uses. The finding count drops dramatically, the findings that remain are all actionable (they are in used packages), and the block-on-Critical gate has a meaningful signal.
Implementing a Tiered Gate Policy
A gate that blocks on any severity is a gate that is bypassed. A gate that is severity-specific and consistently applied is a gate that provides security value.
– name: Scan for Critical CVEs
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}-hardened
exit-code: 1
severity: CRITICAL
format: sarif
output: trivy-critical.sarif
– name: Scan for High CVEs (non-blocking)
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}-hardened
exit-code: 0 # Does not fail the build
severity: HIGH
format: sarif
output: trivy-high.sarif
– name: Upload results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: trivy-critical.sarif
This configuration:
- Blocks builds with Critical CVEs (the ones that represent genuine immediate risk)
- Reports High CVEs without blocking (visible to developers in GitHub’s Security tab)
- Makes findings visible in the developer’s existing workflow without requiring a separate tool
Surfacing Findings Where Developers Look
The GitHub Actions log is where developers look when a build fails. The GitHub Security tab is where they see security findings after the build. Both surfaces matter.
Docker security tool SARIF output: SARIF (Static Analysis Results Interchange Format) is the format GitHub uses for security findings. Scanner output in SARIF format integrates with the GitHub Security tab, where developers see findings alongside code review, not in a separate security portal.
PR annotations: Some scanner integrations support annotating the pull request with findings that relate to code changes in the PR. A finding in a dependency that was added in this PR is directly relevant to the PR; a finding in an existing dependency is lower priority for the PR but still worth tracking.
Automated PR comments: A workflow step that posts a summary of scan findings as a PR comment puts the security information directly in the developer’s code review workflow.
Frequently Asked Questions
How do you integrate container scanning into GitHub Actions without blocking all builds?
The key is scanning the hardened image rather than the raw build artifact. By running a hardening step before scanning, 70-90% of CVEs are eliminated through unused component removal, so the scanner evaluates only packages the application actually uses. Pairing this with a tiered gate policy — blocking only on Critical CVEs and reporting High CVEs without blocking — keeps builds flowing while maintaining meaningful security enforcement.
What is the best approach for container scanning in GitHub Actions CI/CD pipelines?
The most effective container scanning implementation in GitHub Actions combines image hardening before scanning, SARIF output for GitHub Security tab integration, and severity-tiered gates. Building on a hardened image first ensures that scanner findings are actionable rather than noise from unused packages. Uploading SARIF results to GitHub makes findings visible to developers in their existing code review workflow without requiring a separate security portal.
How can you prevent CVE accumulation in container images managed through GitHub Actions?
Automated base image update workflows address the steady-state problem of CVE accumulation. A scheduled workflow checks for upstream base image updates weekly, automatically creates a PR with the updated image digest, and runs the full CI pipeline including hardening and scanning on that PR. If new Critical CVEs are introduced by the update, the PR surfaces the finding and blocks merge until the CVEs are addressed.
Why does container scanning in GitHub Actions fail builds so frequently for existing projects?
Most existing base images carry High CVEs, so a gate that blocks on any severity will fail from the first run. The solution is to adopt a realistic gate policy that blocks only on Critical CVEs after hardening, and to scan the hardened image where unused components have already been removed. This approach makes the security gate reflect actual exploitable risk rather than the raw CVE count across all installed packages including those never used at runtime.
Managing Container CVE Accumulation in GitHub Actions
The steady-state challenge: your initial integration gets the CVE count to a manageable level. New CVEs are disclosed continuously. Without ongoing maintenance, the CVE count grows back.
Automated base image update workflows:
# .github/workflows/base-image-update.yml
on:
schedule:
– cron: ‘0 9 * * 1’ # Monday mornings
jobs:
check-base-image:
runs-on: ubuntu-latest
steps:
– name: Check for base image updates
# Pull the current base image and compare digest to pinned version
# If different, create a PR updating the pinned digest
This workflow checks for upstream base image updates weekly. When a new base image version is available:
- A PR is automatically created updating the pinned image digest
- The standard CI pipeline runs on the PR, including hardening and scanning
- If the updated base image passes the security gate, the PR can be merged
- If it introduces new Critical CVEs, the PR surfaces the finding and blocks merge until addressed
The automated update workflow ensures that base image CVEs are addressed on a regular cadence without requiring manual monitoring of upstream releases.
Integrating container scanning into GitHub Actions well — with hardening before scanning, tiered gates, and automated update workflows — makes security a feature of the CI/CD process rather than an obstacle added on top of it.