Build a container image security gate with Trivy
A container image can pass a functional test while still carrying an unpatched package, a credential left in a layer, or a Dockerfile setting that is unsuitable for the environment where it will run. Those are different problems, so a useful build gate needs to report them separately and make its failure policy explicit.
This guide builds a small, repeatable gate around Trivy 0.73.0. The result is a CI job that scans a pinned container image for high and critical vulnerabilities, checks the source tree for infrastructure-as-code problems and secrets, saves machine-readable reports, and fails only when the policy says it should. It is appropriate for a repository that builds an OCI image and has a controlled CI environment. The commands are documentation-verified; adapt image names, registries, retention, and approval rules to the local delivery process.
Trivy released version 0.73.0 on 3 August 2026. The release is a useful point to review the scanner rather than a reason to change policy blindly. Download the release archive and checksum manifest from the official release, verify the checksum, and test the commands in a non-production runner before making them a required check.
Decide what the gate is allowed to block
Start by separating discovery from enforcement. A scan report can contain an unfixed vulnerability, a dependency that is not reachable by the application, an intentionally accepted exception, or a genuine release blocker. If every finding returns a nonzero status immediately, teams often respond by disabling the scan. If the job always returns zero, the report is easy to ignore.
For an initial gate, block only on high and critical vulnerabilities that have a fixed version, and send other findings to review. That is why the image scan below uses --ignore-unfixed: it limits the displayed vulnerability findings to ones with a published fix. This is a policy choice, not a statement that unfixed vulnerabilities are harmless. Track significant unfixed issues in the normal risk process, especially for internet-facing or privileged workloads.
Keep secrets out of the CI log. Trivy detects several credential types in images, filesystems, and Git repositories. A match should create an incident or rotation task, not be copied into a build comment. Use the report’s path and rule name to locate the exposure, restrict access to the artifact, then revoke or rotate a real credential before removing it from source history.
Finally, keep a narrowly scoped exception file under review. Trivy supports .trivyignore; an exception should identify a specific finding, have an owner and an expiry outside the scanner, and be removed when a fixed image is available. Do not use a broad severity filter or a permanent ignore as a substitute for a decision record.
Install and verify the scanner
Use a versioned binary in the CI image or download it during a controlled bootstrap step. The following example is for Linux x86-64 and uses the archive and checksum file published with the 0.73.0 release. Run this in a temporary directory or a dedicated tools location, not in a source directory that will be included in the image context.
version=0.73.0
archive="trivy_${version}_Linux-64bit.tar.gz"
base="https://github.com/aquasecurity/trivy/releases/download/v${version}"
curl -fLO "$base/$archive"
curl -fLO "$base/trivy_${version}_checksums.txt"
grep " $archive$" "trivy_${version}_checksums.txt" | sha256sum -c -
tar -xzf "$archive" trivy
install -m 0755 trivy /usr/local/bin/trivy
trivy version
The checksum command must print OK before extraction. In a CI image, perform this verification when the image is built and pin the resulting image digest in the workflow. Do not fetch a latest scanner during every production build; that makes a later report difficult to reproduce.
A successful version check should identify Version: 0.73.0. The release includes Sigstore metadata alongside its archives; organizations with an established artifact-verification process can add that verification as well. The important baseline is that a build runs a known scanner binary whose archive was checked before execution.
Scan the image by immutable digest
Scan the exact artifact that the deployment process will promote. A mutable tag such as registry.example.net/payments/api:main can resolve to different content between the scan and deployment. Build and push the candidate in the ordinary pipeline, capture its digest from the registry or build system, then scan name@sha256:....
image="registry.example.net/payments/api@sha256:<image-digest>"
mkdir -p reports
trivy image \
--scanners vuln \
--severity HIGH,CRITICAL \
--ignore-unfixed \
--format json \
--output reports/image-vulnerabilities.json \
--exit-code 1 \
"$image"
--scanners vuln makes the intent unambiguous. For the image command, vulnerability and secret scanning are enabled by default, while misconfiguration scanning is not. Limiting this first command to vulnerabilities gives the security team a report tied to installed operating-system and language packages without mixing it with Dockerfile or secret results.
The --exit-code 1 setting turns a finding that survives the filters into a failed job. Save JSON because it is suitable for an artifact, ticket automation, or a later report comparison. Do not treat a successful exit as proof that the image is safe: it means no finding matched this particular scanner selection, severity threshold, and fixed-version policy.
If the registry requires authentication, use the CI platform’s masked credential mechanism or the documented trivy registry login workflow. Do not put a registry password in an image reference, shell history, source file, or a generated JSON report. Restrict the token to read access to the one registry namespace where possible.
Scan source configuration and secrets separately
A container image scan cannot replace a source review. Run a configuration scan before the image is built so that a Dockerfile, Kubernetes manifest, Helm chart, Terraform plan source, or CloudFormation file can be corrected in the pull request that introduced it.
trivy config \
--severity HIGH,CRITICAL \
--format json \
--output reports/misconfigurations.json \
--exit-code 1 \
.
Trivy automatically identifies supported configuration types in the target directory. Start with high and critical results, then review whether medium findings should become policy after the repository has a clean baseline. A finding about a container running as root may need a Dockerfile change; a Kubernetes finding may need a workload-specific security context. Do not apply a generic manifest fragment without confirming that it matches the application’s ports, volume model, user ID, and platform policy.
Secret scanning needs a different response. It can inspect a working tree without contacting an application or registry:
trivy fs \
--scanners secret \
--format json \
--output reports/secrets.json \
--exit-code 1 \
.
Run this on a clean CI checkout with only the intended revision present. The documented default secret scan skips some paths, including .git, common lock files, archives, and binary formats. That protects performance but also means the result has a scope. If the organization needs to scan generated artifacts or a nonstandard credential file, add a deliberate test for that location instead of assuming every byte was inspected.
Treat a secret finding as sensitive output even when Trivy masks the matched value. Limit report retention, avoid uploading it to a public build log, and make the job fail. After rotation and removal, rerun the scan and confirm that the finding is gone. An ignore entry is rarely an appropriate solution for a live credential.
Put the commands in CI with useful artifacts
The gate can be expressed as separate steps so that the failed policy is visible. This GitHub Actions example assumes the workflow has already built and pushed an image, exposing its immutable digest as IMAGE_REF. Replace the placeholder with the output mechanism used by the build action.
- name: Scan image vulnerabilities
run: |
trivy image --scanners vuln --severity HIGH,CRITICAL \
--ignore-unfixed --format json \
--output reports/image-vulnerabilities.json \
--exit-code 1 "${IMAGE_REF}"
- name: Scan IaC
run: |
trivy config --severity HIGH,CRITICAL --format json \
--output reports/misconfigurations.json --exit-code 1 .
- name: Scan source for secrets
run: |
trivy fs --scanners secret --format json \
--output reports/secrets.json --exit-code 1 .
Upload the reports only to a private artifact store with a short retention period. If the CI platform continues after a failed step to collect artifacts, ensure the final workflow result still fails. A convenient report must not turn a blocking vulnerability into a green build.
For pull requests, make the gate a required status only after testing it against representative repositories. First measure the baseline, assign owners for common findings, and document the exception process. For releases, scan again after the final image digest exists. The same source revision can produce a different artifact if its base image is mutable or the build process is not deterministic.
Verify the gate with controlled failures
Do not validate a security gate by scanning a production registry or by planting a real secret. Use a disposable test repository and a known-safe placeholder pattern that the local policy is expected to find. Confirm that the secret report is protected and that the job returns status 1 without printing a credential value.
For the image policy, choose a non-production test image that has a documented high or critical finding with a fixed version, or temporarily set an intentionally vulnerable dependency in a disposable Dockerfile. Build it, record the image digest, run the image command, and verify that reports/image-vulnerabilities.json exists and the step fails. Then replace the dependency with its fixed version, rebuild to obtain a new digest, and confirm the scan no longer finds that issue.
For configuration, use a disposable Dockerfile or manifest that deliberately violates a documented check, such as running as root when the relevant check applies. Confirm the report identifies the path and check identifier. Correct the configuration in the test repository and rerun the exact command. This proves both halves of the control: an unsafe input blocks promotion, and the intended correction returns the pipeline to green.
The finished system is not a claim that every image is vulnerability-free. It is a reproducible decision point: the same immutable image, scanner version, scanner policy, and reports can be reviewed before promotion. Trivy 0.73.0 provides the scanner; the value of the gate comes from keeping its inputs immutable, its policy narrow and explicit, and its exceptions accountable.