Combo 1 reference implementation of immutable Go releases, using release-please + GoReleaser. Part of the go-release-demo evaluation project comparing three release toolchains.
Cutting a versioned Go release where:
- The git tag cannot be moved, deleted, or re-created (tag ruleset).
- The release assets cannot be edited after publication (immutable-releases setting).
- The artifacts are signed by a verifiable workflow identity via cosign keyless (Sigstore, OIDC).
- The build has SLSA L3 provenance attestations proving how it was built.
- The build is reproducible: the same commit produces byte-identical binaries, testable via a rebuild-and-diff CI job.
- The release is atomic: the tag only exists if the full build, sign, and publish succeeded — no partial state ever reaches a consumer.
- Version proposals surface in a Release PR — release-please keeps a running proposal open on
mainthat shows exactly what will be released and lets humans review it before merging.
| Concern | Tool |
|---|---|
| Next version + Release PR + changelog | release-please via googleapis/release-please-action@v4 |
| Build, sign, SBOM, release | goreleaser |
| Keyless signing | cosign |
| Build provenance | slsa-github-generator |
| SBOM | syft (invoked by GoReleaser) |
Download for your platform from the latest release:
# Example: linux/amd64
curl -fsSL -o demo.tar.gz \
https://github.com/bonial-oss/go-release-demo-release-please/releases/download/v0.1.0/demo_0.1.0_linux_amd64.tar.gz
tar xzf demo.tar.gz
./demo version
./demo verifySee docs/verification.md for cut-and-paste commands
that check the signature, provenance, and reproducibility of any downloaded
archive.
The bundled demo verify subcommand does the same check in-binary — the
binary you downloaded proves to you that it's the binary the release
workflow produced.
- Merge normal PRs to
maincontainingfeat:/fix:/ etc. commits. - On every push to
main, theReleaseworkflow runs release-please. release-please reads the commit history since the last tag and either:- opens or updates an open Release PR titled
chore(main): release X.Y.Zshowing the proposed next version and generateddocs/CHANGELOG.mddelta; or - detects a just-merged Release PR and emits
release_created=true, at which point downstream jobs (goreleaser,slsa,promote) build and publish the release.
- opens or updates an open Release PR titled
- Three sequential jobs after
release_created=true: goreleaser (build, sign, SBOM, create draft release with all assets) → slsa (attach SLSA L3 provenance to the draft) → promote (gh release edit --draft=false— the only write to a published release). - If any step fails, no tag reaches consumers (the release stays a draft or doesn't exist at all). Delete the draft (if any) and re-dispatch the workflow.
A manual workflow_dispatch path is supported for dry-run previews and
forced re-runs — see .github/workflows/release.yaml.
The verify-release.yaml workflow
runs rebuild-and-diff (reproducibility) and cosign + SLSA signature
verification against every released asset. It is defined to trigger on
release: types: [released] but does not auto-trigger today — the
promote job publishes the release under secrets.GITHUB_TOKEN, and
GitHub's chain-prevention rule
suppresses the released event in that case.
After each release, dispatch verify-release manually:
gh workflow run verify-release.yaml \
--repo bonial-oss/go-release-demo-release-please \
--ref main \
--field tag=v0.1.0 # substitute the actual release tagPlanned fix: switch promote to a dedicated bonial-release GitHub
App token (see .rulesets/README.md). The App identity is distinct from
github-actions[bot], so the released event fires normally under it.
make install-tools # install reuse into .venv-reuse
make lint # reuse-lint + md-lint + go-lint
make test # go test ./... -race -cover
make build # go build ./...Individual commits must be Conventional Commits; this is enforced by CI (.github/workflows/commitlint.yaml). The main branch protects against direct pushes; merge via PR.
Apache 2.0. See LICENSE and REUSE.toml.