Publish the image as latest instead of main, with provenance, SBOM, and a scan - #2294
Merged
Conversation
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.
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.
latest instead of main, with provenance, SBOM, and a scan
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Closes #2184.
Currently the only published tag is
main, which was never a deliberate choice: it's whatdocker/metadata-actionemits by default when notagsinput is given. With releases cut on demand, the name misleads, because the image can sit several commits behind the branch it names. This PR publisheslatestinstead, which promises only "the most recent release". It also makesdocker run ghcr.io/felladrin/minisearchwork with no tag at all, which currently fails.Releases stay on demand. Publishing on every push to
mainturns a README typo fix into a full multi-platform build, so theworkflow_dispatchtrigger is unchanged.latestis guarded byenable={{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
buildon/status, which is what maps a running instance back to a commit.provenanceandsbomneed 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 listingunknown/unknownentries.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,CRITICALon the built image:node:ltsnode:lts-slim11 findings is a list someone reads. 75 is a badge nobody clicks.
What changed
.github/workflows/publish-docker-image.ymllatestinstead ofmain; attach provenance and SBOMDockerfilenode:ltstonode:lts-slim, reinstallinggit,curl,openssl,ca-certificates.github/workflows/scan-docker-image.ymlREADME.mddocs/development-commands.mdScope note
The issue also asked for semver tags, a changelog, and a
package.jsonbump. Those are dropped. The version inpackage.jsonis never read (private: true), so bumping it adds a second source of truth that drifts fromappVersion. And the version the app already reports is a valid semver 2.0.0 string on its own (semver.valid('2026.8.10+f1c8d2e')returns2026.8.10, since the spec ignores build metadata), so adopting a separate scheme would mean replacing something that already works.How to test
docker build -t minisearch:slim .docker run -d --rm --name ms -p 7861:7860 minisearch:slimdocker inspect --format '{{.State.Health.Status}}' msshould reachhealthy. This is the check that matters most:HEALTHCHECKcallscurl, which the slim base does not ship, so a missed dependency shows up here.curl -s http://localhost:7861/statusshould 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
rerankerServiceStatusandwebSearchServiceStatushealthy. The SearXNGpip installresolveslxmlfrom a wheel, so no build toolchain is needed on the slim base.Known limitations
npm run lintexits 1 on this branch, but it does so onmaintoo (knip's "Unlisted binaries"), so it's pre-existing and untouched here.npm run format:checkpasses.Post-merge actions
maintag from the GHCR package, so pulls fail loudly instead of silently freezing on the last image published under it.