Container/Image Security
What is container security?
Container security scanning detects vulnerabilities in the OS packages and application dependencies bundled into a container image, along with Dockerfile misconfigurations and exposed secrets, before deployment. Blocking these at the build stage is especially important because once an image is deployed, the same vulnerability propagates to every instance.
The YAML and commands on this page are examples that show the essentials. For a complete, copy-and-run pipeline (including policy files and a sample app), see the Best Practice repository.
The examples below keep mutable tags such as @v7 for readability. A tag can be repointed to a different commit later, so in production pin each action to a full commit SHA and grant only the permissions a job needs with a permissions: block. See Pipeline Security for the reasoning and the procedure.
Tool Comparison
| Tool | Features | Detection range | License |
|---|---|---|---|
| Trivy | All-in-one, fast, simple setup | Image·Filesystem·IaC·Secret | Apache-2.0 |
| Grype | Optimized for SBOM integration | Image/Filesystem | Apache-2.0 |
| Dockle | Checks Dockerfile best practices | Image configuration | Apache-2.0 |
We recommend Trivy as a single container security tool. If you already use Grype in your SCA pipeline, you can unify image scanning on Grype as well.
Trivy setup
Basic usage
# scan local image
trivy image myapp:latest
# scan filesystem (before build)
trivy fs .
# Generate SBOM
trivy image --format cyclonedx myapp:latest \
-o sbom.cdx.json
# Severity filter
trivy image --severity HIGH,CRITICAL myapp:latest
GitHub Actions
# .github/workflows/container-security.yml
name: Container Security — Trivy
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
trivy:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v7
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
- name: Scan image — vulnerability
uses: aquasecurity/trivy-action@v0.36.0
with:
image-ref: myapp:${{ github.sha }}
format: table
exit-code: 1
severity: HIGH,CRITICAL
ignore-unfixed: true
- name: Scan image — secret
uses: aquasecurity/trivy-action@v0.36.0
with:
image-ref: myapp:${{ github.sha }}
scanners: secret
exit-code: 1
- name: Upload SBOM
uses: aquasecurity/trivy-action@v0.36.0
with:
image-ref: myapp:${{ github.sha }}
format: cyclonedx
output: sbom.cdx.json
- uses: actions/upload-artifact@v7
with:
name: container-sbom-${{ github.sha }}
path: sbom.cdx.json
retention-days: 90
GitLab CI
# .gitlab-ci.yml (container-security job section)
container-security:
stage: test
image: docker:27
services:
- docker:27-dind
variables:
DOCKER_TLS_CERTDIR: '/certs'
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
script:
# the docker image does not include trivy, so install it
- apk add --no-cache curl
- curl -sSfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh
| sh -s -- -b /usr/local/bin
- docker build -t $IMAGE_TAG .
# vulnerability scan
- trivy image
--severity HIGH,CRITICAL
--exit-code 1
--ignore-unfixed
$IMAGE_TAG
# secret scan
- trivy image
--scanners secret
--exit-code 1
$IMAGE_TAG
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
Trivy policy file
Vulnerabilities not yet patched upstream cannot be fixed by the development team, so excluding them lets you focus on actionable findings.
# .trivyignore.yaml
vulnerabilities:
- id: CVE-2023-XXXXX
paths:
- usr/lib/some-lib
statement: 'Path unused in container — security team approved 2024-02-01'
secrets:
- id: aws-access-key-id
paths:
- test/fixtures/dummy.env
In Practice
The open source project TRUSCA scans the worker image it builds with Trivy — the image-scan job in
ci.yml. HIGH or CRITICAL findings fail the build.
Two details are worth borrowing. Exceptions live in .trivyignore per CVE with a written reason
for why it is unreachable, and the policy states that those entries are re-evaluated every 180
days or on the next release of the bundled tool. Putting an expiry on exceptions is what keeps them
from being forgotten.
Dockerfile security best practices
- Use a minimal base image: Choose
alpineordistrolessoverubuntu. Fewer packages mean a smaller vulnerability surface. - Avoid running as root: Specify a non-root user with the
USERinstruction. Running as root amplifies the damage if a container is compromised. - Multi-stage builds: Keep build tools and source code out of the final image. This reduces both image size and attack surface at once.
- No secrets in ARG/ENV: Never pass secrets as build arguments or environment variables. They persist as plain text in the image layers.
- Pin versions: Use explicit tags like
FROM ubuntu:22.04. Usinglatestcan silently introduce unexpected vulnerabilities.
Trivy's repository was hijacked in 2026-03 and malicious versions were briefly published (since recovered). Pin action and image references to version tags (ideally commit digests) instead of moving refs like latest or master.
The browser-based result analyzers offered on the SCA, SAST, secret detection, and IaC pages do not yet exist for this topic. If you need help interpreting results, feed the SBOM generated by Trivy (sbom.cdx.json) into the analyzer on the SCA page.
Next steps
- Infrastructure code security: IaC security
- Full pipeline integration: Pipeline Design
- Continuous monitoring of images after deployment: Monitoring and Automated Remediation