Releasing
A release starts when a signed v* tag is pushed. The workflow .github/workflows/release.yml does the
rest. This page is the release manager’s checklist, followed by the one-time setup.
What the workflow does
releasechecks that the tag is a version and points at a commit onmain. It buildsdummyrunanddummyrun-otelwith GoReleaser for Linux, macOS and Windows on amd64 and arm64. It writes the archives, the SBOMs andchecksums.txt, signs the checksums with cosign and attests their build provenance. It creates a draft GitHub release with the notes fromCHANGELOG.md.imagebuilds the image on the released Linux binaries and smoke tests it before pushing. It pushesghcr.io/dummy-run/dummyrunfor amd64 and arm64 by digest only. It signs the digest and attests its provenance and SBOM. Only then does it give the digest the version, minor version andlatesttags.publishtakes the release out of draft. It runs only when both earlier jobs pass, so a failed run never leaves a public release without its image.
The jobs hold only the permissions they need. Only release reads the release environment, which holds the
Homebrew token.
Checklist
Check that
mainis green: CI, Quality, Engine and Release check.Check that
CHANGELOG.mdhas a section for the version with the entries for it. Replaceunreleasedin its heading with the release date, as in## [0.1.0] - 2026-10-21. Open a pull request, get it merged and pullmain. Leave## [Unreleased]above it, empty.Check the pins. The browser versions in
docker/Dockerfilemust match.github/workflows/engine.yml, and the Release check workflow fails when they differ.Rehearse the build on your machine if you changed
.goreleaser.yaml.scripts/goreleaser-check.shchecks the configuration andgoreleaser build --snapshot --single-target --cleanbuilds one target.Tag the merged commit with a signed tag and push only the tag.
git switch main && git pull --ff-only git tag -s v0.1.0 -m "v0.1.0" git push origin v0.1.0Watch the run with
gh run watch. If the environment has a required reviewer, approve thereleasejob when it waits.Verify the artefacts with the commands under “Verify a download” and “Verify the image” in INSTALL.md.
- Download one archive and
checksums.txt. - Run
cosign verify-blobon the checksums file. - Run
sha256sum --ignore-missing -con the archive. - Run
gh attestation verifyon the archive. - Run
cosign verifyandgh attestation verify oci://...on the image. - Start the image with
docker run --rm ghcr.io/dummy-run/dummyrun:0.1.0 version.
- Download one archive and
Check the release page. It shows the notes and 12 archives, which are 2 commands on 6 platforms. It also shows an SBOM for each archive,
checksums.txtandchecksums.txt.sigstore.json.Announce it. Link the release page and the changelog section.
After the first release, remove the “no tagged version” wording. It is in the README banner, in the
Supported versions section of SECURITY.md and in the install note of docs/ENGINE.md. Add a Homebrew
section to docs/INSTALL.md when the tap is on.
When a release fails
The release workflow checks the tag and the main commit before it builds anything. A run that fails that check leaves no draft and no image. Fix the cause and push the tag again.
A run that fails after the check leaves the release in draft, since only publish makes it public. The image gets its version, minor and latest tags once it is signed and attested. A failure before that leaves only an untagged digest. To retry:
- If the
imagejob gave the digest its tags, orpublishfailed after it, delete those tags in the package settings ofghcr.io/dummy-run/dummyrun. - Fix the cause.
- Delete the draft release and its tag on GitHub.
- Push the tag again on the fixed commit.
This cleanup applies only to a release that was never published. Never move a published tag. A published release is final, so ship a new patch version instead.
One-time setup
The repository owner does these steps once.
The release environment
The release environment limits who can publish. Create it and restrict it to v* tags.
gh api -X PUT repos/dummy-run/dummy-run/environments/release \
-F deployment_branch_policy[protected_branches]=false \
-F deployment_branch_policy[custom_branch_policies]=true
gh api -X POST repos/dummy-run/dummy-run/environments/release/deployment-branch-policies \
-f name='v*' -f type=tag
Add the owner as a required reviewer if you want a pause before each release. Add a tag ruleset for v* so
only maintainers can create and nobody can move or delete one.
Homebrew, when you want it
The formula is off until these exist.
- Create the public repository
dummy-run/homebrew-tapwith aFormuladirectory and a README. - Create a fine-grained personal access token with the tap repository as its only repository and the permission Contents: read and write. Give it an expiry and put the date in the team calendar.
- Store it as the secret
HOMEBREW_TAP_TOKENof thereleaseenvironment. - Set the repository variable
HOMEBREW_TAP_ENABLEDtotrue.
gh secret set HOMEBREW_TAP_TOKEN --env release --repo dummy-run/dummy-run
gh variable set HOMEBREW_TAP_ENABLED --body true --repo dummy-run/dummy-run
The next release then pushes Formula/dummyrun.rb to the tap. The formula installs the macOS and Linux
archives, and its test runs dummyrun version. The tap is updated while the release is still a draft. A failure in the image job or in publish can leave a formula that points at a draft. After you fix the failure and tag again, check that Formula/dummyrun.rb points at the new release before users install from the tap. Repair it if it is stale. Then add a Homebrew section to docs/INSTALL.md.
Package visibility
GitHub creates the container package as private on its first push. After the first release, open the package
settings of ghcr.io/dummy-run/dummyrun, connect it to the repository and set its visibility to public.
Anonymous pulls need this, including the pulls of the hosted runner.
Tooling versions
All actions are pinned by commit. GoReleaser is v2.18.3, cosign v3.1.3 and syft v1.55.0, set at the top of
release.yml. Dependabot proposes action and base image updates weekly. Change a tool version in
release.yml and release-check.yml together.