Skip to main content
A Dockerfile build can record what is inside the image, and the control plane can check that list for known vulnerabilities before the release goes live. Both are off by default. Nothing runs while idle. There is no resident scanner: a scan starts a container, reads one file, prints JSON and the container is removed again.

Turn it on

1

Record SBOMs

Set APP_BUILD_ATTEST=true on the control plane and restart it. From then on each Dockerfile build records an SBOM.
2

Turn on scanning for an app

Open the app, then Deploy settings, then Supply chain, and turn on scanning.
Each deploy’s Supply chain section (in the deployments drawer and on the per-app deploy page) shows the package count, the first packages, a license summary and vulnerability counts by severity, with a Scan now button and a download link for the SBOM.

What a scan costs

  • The first scan pulls the scanner image (default docker.io/aquasec/trivy:0.65.0, or docker.io/anchore/grype:v0.99.1 with APP_SCANNER=grype). Expect each image to be on the order of hundreds of MB. The image stays on the host after the scan.
  • The scanner downloads its vulnerability database on the first scan and refreshes it later. It is cached in a Docker volume named after the brand short name with a -scanner-cache suffix.
  • Each scan runs in one container with APP_SCAN_MEMORY_MB (default 1024) of memory, APP_SCAN_CPUS (default 1) CPUs and a 256 process limit, all capabilities dropped, no-new-privileges, removed afterwards. It has network access, because the database download needs it. Only the SBOM is copied into it: no app data, secrets or Docker socket.
  • A deploy waits for the scan before it goes live, up to APP_SCAN_TIMEOUT (default 5 minutes). A scanner that fails or times out never blocks a release.
  • Attestations add work to every build (BuildKit runs a scanner image over the build result). That cost is why APP_BUILD_ATTEST is off by default.

The gate

A blocked deploy fails with the reason, desired state is not changed and the previous release keeps serving. If the scanner is unavailable, times out or fails, the gate fails open and the deploy records that the scan did not complete. A scanner report that is not in the expected format counts as a failed scan, never as a clean one. Every service of a multi-service deploy is gated on its own settings, and one blocked service does not let the others skip their gate. Changing scanning settings never arms, restores or extends an override: turning the gate away from block_on_critical disarms it. To let one blocked release through, an operator arms an override with a reason (Deploy settings, Supply chain, or levelrail-cli apps scan override web --reason "..."). It applies to the next release only, expires after APP_SCAN_OVERRIDE_TTL (default 1 hour), and the reason is recorded in the app timeline and the audit log. A deploy freeze still applies as usual: the gate runs after a build, so it never releases a frozen deploy.

What is not covered

  • Only Dockerfile builds get an SBOM. Railpack builds, image deploys, static sites and builds dispatched to a remote node record none. Those deploys show no supply chain section data.
  • Provenance is mode=min and is recorded, not signed or verified.
  • Scans run against the SBOM, so they find what the SBOM lists. They do not read the image.

Retention

SBOM documents live in DataDir/supplychain/<app>/<deploy>.sbom.json, never in the database. The control plane keeps the newest APP_SBOM_KEEP_PER_APP (default 20) documents per app. An older document is deleted but its record, with the package count, licenses and vulnerability counts, stays. Records and files are removed when the app is deleted or when the deploy they belong to no longer exists. An hourly sweep also removes orphan files and any scanner container older than twice the scan timeout.

API

GET /api/v1/apps/{name}/deploy-attempts and GET /api/v1/deployments gain two nullable fields: sbom_packages and vuln_counts. The MCP server exposes get_deploy_sbom and get_deploy_vulnerabilities, both read only.

Settings

Last modified on October 12, 2026