Publish the container images through the org's shared pipeline - #120
Merged
Merged
Conversation
Ilyes512
force-pushed
the
ci/reuse-org-image-workflows
branch
2 times, most recently
from
September 22, 2026 10:20
fe498c9 to
ec422e1
Compare
Replaces the hand-rolled `images` job with specsnl/github-actions' build-go-cli.yml and merge-go-cli.yml at 2.4.1: one build job per platform on a runner of that architecture, then a manifest merge of the digests. Login, buildx, the tag policy, the OCI labels, `provenance: false` and a layer cache all come with it. What stays here is what only this repository can know: that the stages to publish are `binary` and `debian`, that they publish under ghcr.io/specsnl/labelsync, and that the version build arg is called LABELSYNC_VERSION. The version itself is not passed — the shared workflow defaults it to the tag without its leading v, which is what GoReleaser injects, so the image and the tarball cut from one tag agree about what they are. The debian image moves from the nested ghcr.io/specsnl/labelsync/debian to a `-debian` tag suffix on the one image name, which is how the shared pipeline publishes a CLI's base-image variants and how node, python and postgres publish theirs. The nested package is frozen at 0.1.0-rc.4. The workflow tests follow the new shape: the four jobs and their pairing, the native runner per platform, the version build arg matching the Dockerfile's ARG, `packages: write` on each calling job, and one shared-workflow ref across the whole tree. The tag policy is no longer asserted here — it lives in the shared workflow now.
A broken Dockerfile should fail on the pull request, not while a tag is being cut. A matrix over both stages and both architectures builds through specsnl/github-actions' build-image action and runs test/image.bats against the result. The action is called directly rather than through build-go-cli.yml because the image has to be built and run in the same job: a reusable workflow would load it into a daemon this job cannot reach. `load: true` builds one platform into the runner's own daemon and reports the reference to run, which is also why each leg needs a runner of its own architecture — a foreign build can be compiled but not executed. The script asserts the version the binary reports (built with a string no fallback can produce, so a renamed build arg is loud rather than silent), the uid it runs as, the CA bundle, and a bind-mounted config — both a valid one, which gets as far as failing on the token, and an invalid one, which comes back with its error_kind. The two stage-specific properties, the shell and /etc/passwd, are skipped on the stage that does not have them. `task image:smoke` runs the same file locally, through a bats stage in the Dockerfile and a socket proxy, replacing `task images` and its version-only check. `task image:build` is the build half on its own.
Distribution gains the four jobs of configuration that are all this repository still owns, the new tag table including the v-prefixed alias the shared pipeline emits, why one runner per platform replaced one multi-platform build, and what the pull-request guard asserts. The manual step is one package now rather than two. The usage pages and the README carry the rename: the debian image is ghcr.io/specsnl/labelsync:0.1-debian, not a package of its own.
`task lint:docker` runs hadolint through a compose service under the lint profile, the same shape as golangci-lint, and joins `task checkall`. CI runs the task itself rather than hadolint/hadolint-action, which ships its own hadolint build and would put the version in a second place to keep in sync. One finding to clear, which specs-cli had already fixed: the base stage kept its apt lists after installing. Version pinning is off in .hadolint.yml: pinning every apt/apk package on top of an already pinned base image trades a reproducible build for one that breaks the moment the distro moves a package version.
Ilyes512
force-pushed
the
ci/reuse-org-image-workflows
branch
from
September 22, 2026 10:29
ec422e1 to
72a8b9f
Compare
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.
Summary
imagesjob withspecsnl/github-actions'build-go-cli.yml/merge-go-cli.ymlat 2.4.3 — one build per platform on a native runner, then a manifest merge. Login, buildx, the tag policy, OCI labels,provenance: falseand a layer cache all come with it.ghcr.io/specsnl/labelsync/debianto a-debiantag suffix on the one image name, which is how the shared pipeline publishes a CLI's base-image variants.test/image.bats.task lint:docker(hadolint, pinned incompose.yml) totask checkalland to CI, matching specs-cli.release_test.gofor the new shape and addsci_test.gofor the guard.Notes for review
What stays in this repository is the three things only it can know: the stages to publish (
binary,debian), the image name, andversion-build-arg: LABELSYNC_VERSION. Noversion:input — the shared workflow defaults it to the tag without its leadingv, which is what GoReleaser injects, so the image and the tarball cut from one tag agree about what they are.2.4.3 is the floor, not just the newest tag. Up to 2.4.2 the shared merge job carried
type=raw,event=workflow_dispatch,value=latest;event=is an attribute oftype=ref, so metadata-action dropped it and the directive collapsed to an unconditional:lateston every run. Neitherenable=falseon the intended latest tag norflavor: latest=falsegoverns a second raw tag. Every labelsync tag so far is av0.1.0-rc.Nprerelease, so under 2.4.2 the next rc would have pointed:latestat a release candidate — the exact outcome the tag policy is meant to prevent. specs-cli hit it onv0.0.14-rc.2.The debian rename is a breaking change to an address, deliberately taken now:
ghcr.io/specsnl/labelsync/debianis frozen at0.1.0-rc.4and gets nothing further. Four release candidates is the whole exposure, and the suffix form is whatnode,pythonandpostgresdo.The shared pipeline also emits
type=ref,event=tag, sov1.2.3is published as an alias onto the same digest as1.2.3. Additive;:1.2.3stays the form the docs and the CI recipe pin.Native runners, not QEMU. The guard has to run what it builds, so each leg needs a runner of its own architecture. The Dockerfile's cross-compilation still holds and is still tested — it is what keeps a local
--platform linux/arm64build out of emulation.task image:smokeruns the same bats file CI does, against both images: the injected version (built with a string no fallback can produce, so a renamed build arg is loud rather than silent), the uid, the CA bundle, and a bind-mounted config — valid, which gets as far as failing on the token, and invalid, which comes back with itserror_kind.hadolint had one finding on the existing tree — the base stage kept its apt lists.
checkallgrew a step, soAGENTS.md's description of the sequence moved with it.Needs a human
Image (...)jobs andDockerfile lintare not in the branch ruleset's required checks, so they run but do not gate. Worth adding if that is wanted.ghcr.io/specsnl/labelsyncis already public, and the variant lands inside it.No Docker tag has been generatedin the build job logs. 2.4.3 leavesbuild-imagewith no tag directive on a tag push; it pushes by digest, so nothing is lost, and the manifest'sorg.opencontainers.image.versionstill comes from the merge job.Verified locally
task checkall(includinglint:docker),task image:smoke— 14 assertions across both images —task docs:build, andactionlintover both workflows. All green.