Keeping container images secure is a critical task in any modern DevOps workflow. Two popular tools that help achieve this are Grype and Syft. With these tools, you can automatically generate a Software Bill of Materials (SBOM) and perform vulnerability scans against the CVE Database for your container images. However, a common challenge arises when running these tools inside GitLab CI: the containers provided by their vendors are distroless and cannot be used by GitLab Runner (which often requires a full Linux distribution in order to run Docker-in-Docker or other tasks).
What Are Grype & Syft?
-
Syft: A tool used to create a detailed SBOM (Software Bill of Materials) of your container images, including all packages and dependencies.
-
Grype: A vulnerability scanner that uses the SBOM data to detect potential security threats and missing patches within your container images.
The Challenge
When running scans in GitLab CI, you might try to pull the official Grype or Syft containers only to discover they’re minimal “distroless” images, which are incompatible with the default GitLab Runner. Also by default they try to use docker or podman. Typically, using containers that require certain privileges (like Docker-in-Docker) or a full Linux distribution can be problematic or insecure in a Gitlab Runner.
The Solution: Nix
By using Nix, you can simply use Grype and Syft in a plain, unprivileged environment. Furthermore, by using a “special prefix,” you can pull images—even from private registries—without needing special setups for Docker or Podman.
Below are two example jobs in GitLab CI that will illustrate how to automatically create SBOMs and scan for vulnerabilities, all without privileged containers.
Example Job: Generating an SBOM with Syft
syft-container-scaning:
stage: build
image:
name: nixos/nix:2.25.3
entrypoint: [""]
variables:
SYFT_REGISTRY_AUTH_USERNAME: ${CI_REGISTRY_USER}
SYFT_REGISTRY_AUTH_PASSWORD: ${CI_REGISTRY_PASSWORD}
script:
- mkdir reports
- nix-shell -p syft --run "syft scan registry:your-container-image:vx.x.x --output cyclonedx-json=reports/container-sbom.json --output cyclonedx-xml=reports/container-sbom.xml"
artifacts:
paths:
- reports/**.json
- reports/**.xml
when: on_success
expire_in: "30 days"
only:
- tags
Example Job: Vulnerability Scanning with Grype
grype:
stage: build
image:
name: nixos/nix:2.25.3
entrypoint: [""]
needs:
- job: syft-container-scaning
artifacts: true
artifacts:
paths:
- reports/container-vulnerability-report.json
when: always
expire_in: "30 days"
script:
- nix-shell -p grype --run 'grype --fail-on High sbom:reports/container-sbom.json -o cyclonedx-json=reports/container-vulnerability-report.json -o table'
only:
- tags
``
## Conclusion
By leveraging **Nix** to install Grype and Syft, I can seamlessly integrate container scanning into my GitLab CI pipeline without resorting to privileged containers. This approach helps maintain security best practices while providing all the benefits of generating an SBOM and detecting vulnerabilities in your images.
With these two example jobs, I can now confidently automate my container scanning. As soon as your images are built and tagged, Syft generates an SBOM, Grype checks for vulnerabilities, and the pipeline fails (or warns you) when critical issues arise. This helps shift left on security by catching problems early—giving you peace of mind that your container images are secure and up to date.
Ideally I should introduce a maybe daily job to repeat the scanning to find newly discovered vulnerabilites.