Automate Container Security with Trivy in GitHub Actions
Security is a critical component of modern DevOps practices. As development cycles accelerate, manually checking for vulnerabilities in dependencies, containers, and infrastructure becomes impractical. This is where Trivy shines. Trivy is an open-source, simple, and comprehensive vulnerability scanner developed by Aqua Security. It supports:
- Container image scanning (Docker, OCI)
- Filesystem scanning (OS packages, language-specific dependencies)
- Infrastructure as Code (IaC) scanning (Terraform, Kubernetes, CloudFormation)
- SBOM (Software Bill of Materials) generation
By integrating Trivy into your GitHub Actions workflow, you automate security checks ensuring vulnerabilities are caught early and reducing the risk of deploying insecure code.
The Workflow File
Below are example YAML files to get you started. This workflow builds and scans your Docker image for vulnerabilities on every push.
I propose 2 different workflows. The first one, With GitHub Security Report, generates a GitHub compatible Security Report in SARIF that is uploaded to your repo's Security tab via a dedicated action: github/codeql-action/upload-sarif. After the upload, I re-run Trivy with an exit-code: 1 to get an error if a critical or high vulnerability is found.
The second workflow, Without GitHub Security Report, is a simplified version where the SARIF report is not generated and uploaded. Only the Trivy console output is shown on the runner logs, and the job fails for critical or high vulnerabilities found.
If you are on a GitHub Free or GitHub Pro plan, you can only use GitHub Security Report on repositories that are publicly available. To enable this feature for private or internal repositories, you must upgrade to GitHub Team or GitHub Enterprise with GitHub Code Security and enable Code Security in the repository settings.
name: Trivy example workflow
on:
push:
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build Docker image
uses: docker/build-push-action@v5
with:
outputs: type=docker,dest=${{ runner.temp }}/my-image.tar
- name: Upload image artifact
uses: actions/upload-artifact@v4
with:
name: my-image
path: ${{ runner.temp }}/my-image.tar
retention-days: 1
trivy-scan:
runs-on: ubuntu-latest
needs: build
permissions:
contents: read
actions: read
security-events: write # Needed for Security report upload
steps:
- name: Download image artifact
uses: actions/download-artifact@v5
with:
name: my-image
path: ${{ runner.temp }}
- name: Generate Trivy vulnerability report
uses: aquasecurity/trivy-action@master
with:
input: ${{ runner.temp }}/my-image.tar
format: sarif
output: trivy-results.sarif
severity: HIGH,CRITICAL
exit-code: 0 # The job won't fail on vulnerability findings
- name: Upload Trivy report to GitHub Security tab
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: trivy-results.sarif
category: trivy-scan
- name: Fail Trivy on High/Critical vulnerabilities
uses: aquasecurity/trivy-action@master
with:
input: ${{ runner.temp }}/my-image.tar
format: table
output: trivy-results.sarif
severity: HIGH,CRITICAL
exit-code: 1
skip-setup-trivy: true # Trivy has already been intalled so we can skip it
name: Trivy example workflow
on:
push:
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build Docker image
uses: docker/build-push-action@v5
with:
outputs: type=docker,dest=${{ runner.temp }}/my-image.tar
- name: Upload image artifact
uses: actions/upload-artifact@v4
with:
name: my-image
path: ${{ runner.temp }}/my-image.tar
retention-days: 1
trivy-scan:
runs-on: ubuntu-latest
needs: build
permissions:
contents: read
actions: read
steps:
- name: Download image artifact
uses: actions/download-artifact@v5
with:
name: my-image
path: ${{ runner.temp }}
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
input: ${{ runner.temp }}/my-image.tar
format: table
severity: HIGH,CRITICAL
exit-code: 1 # Change to 0 to not fail the workflow if vulnerability is found
severity input to scan and display different severities of vulnerabilities. Available severities are: UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL.Next Steps: Adding Deployment
These workflows provide a solid foundation for container security scanning. A natural next step is to add a deployment job that runs only after successful security scans. I will propose a guide to explain how to push your Docker images to AWS ECR very soon.