Skip to content

Publish the image as latest instead of main, with provenance, SBOM, and a scan - #2294

Merged
felladrin merged 8 commits into
mainfrom
claude/release-versioning-673f6f
Aug 10, 2026
Merged

Publish the image as latest instead of main, with provenance, SBOM, and a scan#2294
felladrin merged 8 commits into
mainfrom
claude/release-versioning-673f6f

Conversation

@felladrin

@felladrin felladrin commented Aug 3, 2026

Copy link
Copy Markdown
Owner

Description

Closes #2184.

Currently the only published tag is main, which was never a deliberate choice: it's what docker/metadata-action emits by default when no tags input is given. With releases cut on demand, the name misleads, because the image can sit several commits behind the branch it names. This PR publishes latest instead, which promises only "the most recent release". It also makes docker run ghcr.io/felladrin/minisearch work with no tag at all, which currently fails.

Releases stay on demand. Publishing on every push to main turns a README typo fix into a full multi-platform build, so the workflow_dispatch trigger is unchanged.

latest is guarded by enable={{is_default_branch}}, so dispatching this workflow from a side branch doesn't move the tag for everyone.

To pin a build, the README now points at the image digest. There's no immutable version tag, because rebuilding the same commit produces a different image anyway (each build bakes a fresh SearXNG secret key, and the base image moves), so a tag naming the commit would promise less than it looks like it promises. The app already reports its own version in the menu and under build on /status, which is what maps a running instance back to a commit.

provenance and sbom need no extra job permissions, since BuildKit pushes the attestations with the registry credentials already in use. They do add attestation manifests, so the GHCR package page will start listing unknown/unknown entries.

Why the base swap came along

The scan is only worth having if its output is actionable, and on the full base it wasn't. Measured with --severity HIGH,CRITICAL on the built image:

Base Total Fixable (what the workflow reports) Image size
node:lts 435 75 2.35 GB
node:lts-slim 104 11 1.6 GB

11 findings is a list someone reads. 75 is a badge nobody clicks.

What changed

File Change
.github/workflows/publish-docker-image.yml Publish latest instead of main; attach provenance and SBOM
Dockerfile node:lts to node:lts-slim, reinstalling git, curl, openssl, ca-certificates
.github/workflows/scan-docker-image.yml New weekly Trivy scan, reporting fixable HIGH/CRITICAL to code scanning
README.md Quick start drops the tag entirely; new section on pinning by digest
docs/development-commands.md Document the published tag and the new workflow

Scope note

The issue also asked for semver tags, a changelog, and a package.json bump. Those are dropped. The version in package.json is never read (private: true), so bumping it adds a second source of truth that drifts from appVersion. And the version the app already reports is a valid semver 2.0.0 string on its own (semver.valid('2026.8.10+f1c8d2e') returns 2026.8.10, since the spec ignores build metadata), so adopting a separate scheme would mean replacing something that already works.

How to test

  1. docker build -t minisearch:slim .
  2. docker run -d --rm --name ms -p 7861:7860 minisearch:slim
  3. docker inspect --format '{{.State.Health.Status}}' ms should reach healthy. This is the check that matters most: HEALTHCHECK calls curl, which the slim base does not ship, so a missed dependency shows up here.
  4. curl -s http://localhost:7861/status should report "webSearchServiceStatus":"healthy", confirming SearXNG still starts (it listens on 8888 inside the container).

Verified locally on arm64: builds clean, healthy after 11s, main page 200, and both rerankerServiceStatus and webSearchServiceStatus healthy. The SearXNG pip install resolves lxml from a wheel, so no build toolchain is needed on the slim base.

Known limitations

  • The amd64 half of the matrix is unverified, since I only built arm64.
  • npm run lint exits 1 on this branch, but it does so on main too (knip's "Unlisted binaries"), so it's pre-existing and untouched here. npm run format:check passes.

Post-merge actions

  • Cut the first release from this scheme, then delete the main tag from the GHCR package, so pulls fail loudly instead of silently freezing on the last image published under it.

The only published tag was `main`, and `workflow_dispatch` was the only
trigger, so it moved at unpredictable times and self-hosters had nothing to
pin or roll back to. The app already reports a version (`appVersion`, CalVer
plus the commit hash), but the registry exposed none of it.

Publish on every push to `main`, and add `sha-<short>`, `YYYY.M.D` and
`latest` tags. The date pattern mirrors `getSemanticVersion`, so a running
instance reporting `2026.8.3+f1c8d2e` maps to the `sha-f1c8d2e` tag. Also
attach provenance and SBOM attestations, and document pinning in the README.
`node:lts` ships a large Debian package set the app never touches, which is
also where most of the image's vulnerability findings come from. Switch to
`node:lts-slim`, reinstalling the tools the full image provided implicitly:
`git` for the SearXNG checkout and the build's commit hash, `curl` for the
HEALTHCHECK, `openssl` for the SearXNG secret key, and `ca-certificates`.

Add a weekly Trivy scan reporting to code scanning. It's deliberately not
tied to pull requests, since findings arrive when upstream publishes an
advisory rather than when someone changes the app.
…sion

The tag set landed as four (sha-, date, latest, main) alongside publishing
on every push to main, which turned a README typo fix into a full release.
Keep releases on demand, and cut the set down to the two that earn their
place: `latest` for the common case, and `YYYY.M.D-<short-sha>` as the
immutable anchor for pinning and rollback.

`main` is dropped. It was never a deliberate choice, only the default output
of `docker/metadata-action` when no `tags` input is given, and with on-demand
releases the name misleads: the image can sit several commits behind the
branch it claims to be. `latest` promises only "the most recent release",
which is what it is. Self-hosters pinned to `:main` need to move; the tag
stays frozen at the last image published under it.

The version tag is a single composite rather than separate date and sha
tags, so one string carries both the readable date and the exact commit.
It mirrors `getSemanticVersion`, with `+` replaced by `-` because Docker
tag references reject `+`.

`latest` is guarded by `is_default_branch`, since it is now the only moving
tag and a dispatch from a side branch would otherwise move it for everyone.
@felladrin felladrin changed the title Publish immutable image tags, with provenance, SBOM, and a scan Publish a pinnable image tag, with provenance, SBOM, and a scan Aug 10, 2026
The two-tag scheme from the previous commit (`latest` plus an immutable
`YYYY.M.D-<short-sha>`) buys less than it costs. Rebuilding the same commit
produces a different image anyway, since each build bakes a fresh SearXNG
secret key and the base image moves, so the version tag would promise "this
commit" rather than "this image". Pinning one exact build is what the digest
is for, and the digest needs no workflow support at all.

So `latest` is the only published tag, and the README documents pinning by
digest. The app keeps reporting its own version in the menu and on `/status`,
which is what maps a running instance back to a commit.
@felladrin felladrin changed the title Publish a pinnable image tag, with provenance, SBOM, and a scan Publish the image as latest instead of main, with provenance, SBOM, and a scan Aug 10, 2026
@felladrin
felladrin marked this pull request as ready for review August 10, 2026 14:25
@felladrin
felladrin merged commit 20e096e into main Aug 10, 2026
6 of 7 checks passed
@felladrin
felladrin deleted the claude/release-versioning-673f6f branch August 10, 2026 14:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Publish immutable image tags, with provenance, SBOM, and a scan

1 participant