A minimal container security pipeline that builds an image, generates an SBOM, then runs CVE and license scans—all via Dagger and Daggerverse modules.
- Build – Apko builds a minimal Wolfi-based Python image and publishes it to
ttl.sh. - SBOM – Syft scans that image and produces a Software Bill of Materials (e.g.
sbom.json+report.md). - CVE scan – Grype scans the SBOM and reports known vulnerabilities (using
.grype.yaml). - License scan – Grant scans the SBOM and reports license compliance (using
.grant.yaml).
Jobs 3 and 4 run in parallel after the SBOM job; each gets the same SBOM artifact.
| Module | Purpose |
|---|---|
apko |
Build and publish minimal container image (Wolfi + Python) |
syft |
Generate SBOM from the built image |
grype |
Scan SBOM for CVEs |
grant |
Scan SBOM for license compliance |
| Path | Description |
|---|---|
apko/container.yaml |
Apko image definition (Wolfi, Python 3.12) |
.github/workflows/minimal-python-app.yaml |
CI/CD workflow |
.grype.yaml |
Grype config (e.g. ignore list) |
.grant.yaml |
Grant config (license policy) |
Current image definition (apko/container.yaml):
contents:
keyring:
- https://packages.wolfi.dev/os/wolfi-signing.rsa.pub
repositories:
- https://packages.wolfi.dev/os
packages:
- wolfi-base
- python-3.12-base
entrypoint:
command: /usr/bin/python3
work-dir: /app
archs:
- amd64Workflow name: apko-minimal-python-scan
Triggers: push/PR to main, or manual workflow_dispatch.
Shared env: IMAGE_REF: ttl.sh/dagger-apko-${{ github.run_id }}:12h (image is reused by Syft).
Job: build-apko-image
Builds from apko/container.yaml and publishes to IMAGE_REF.
- name: Build with apko image and publish
uses: dagger/dagger-for-github@v8.2.0
with:
version: "latest"
module: github.com/hampusctl/daggerverse/apko@main
call: |-
build \
--config-file=apko/container.yaml \
publish \
--address=${{ env.IMAGE_REF }}Job: generate-sbom-apko-image (needs: build)
Scans the published image and writes syft-output/ (e.g. sbom.json, report.md).
- name: scan apko image
uses: dagger/dagger-for-github@v8.2.0
with:
version: "latest"
module: github.com/hampusctl/daggerverse/syft@main
call: |-
scan --image=${{ env.IMAGE_REF }} \
export \
--path=syft-outputAfter the step:
syft-output/report.mdis appended to the job’s GITHUB_STEP_SUMMARY.syft-output/is uploaded as artifactsyft-report.
Job: cve-scan-apko-image (needs: SBOM job)
Downloads syft-report, runs Grype on syft-output/sbom.json with .grype.yaml, writes grype-output/.
- name: Download syft output directory
uses: actions/download-artifact@v4
with:
name: syft-report
path: syft-output/
- name: Scan SBOM with grype module
uses: dagger/dagger-for-github@v8.2.0
with:
version: "latest"
module: github.com/hampusctl/daggerverse/grype@main
call: |-
scan \
--sbom=syft-output/sbom.json \
--config=".grype.yaml" \
export \
--path=grype-outputAfter the step:
grype-output/report.mdis appended to the job’s GITHUB_STEP_SUMMARY.grype-output/is uploaded as artifactgrype-report.
Job: license-scan-apko-image (needs: SBOM job)
Downloads syft-report, runs Grant on syft-output/sbom.json with .grant.yaml, writes grant-output/.
- name: Download syft output directory
uses: actions/download-artifact@v4
with:
name: syft-report
path: syft-output/
- name: License scan SBOM with grant module
uses: dagger/dagger-for-github@v8.2.0
with:
version: "latest"
module: github.com/hampusctl/daggerverse/grant@main
call: |-
scan \
--sbom=syft-output/sbom.json \
--config=".grant.yaml" \
export \
--path=grant-outputAfter the step:
grant-output/report.mdis appended to the job’s GITHUB_STEP_SUMMARY.grant-output/is uploaded as artifactgrant-report.
After a workflow run:
- Open each job’s Summary and check that Syft, Grype, and Grant reports are visible.
- Download artifacts:
syft-report– SBOM and Syft reportgrype-report– CVE scan reportgrant-report– License scan report
- Confirm:
syft-output/sbom.jsonexists in the Syft artifact.report.mdexists in Syft, Grype, and Grant outputs.
- Images are published to ttl.sh and expire after 12 hours.
- All Dagger steps use
callwith|-for multiline readability (no trailing newline). - Grype and Grant use repo config:
.grype.yaml(e.g. CVE ignore list),.grant.yaml(license policy).