Releasing
For maintainers. A release is a tag vX.Y.Z on main. Pushing it starts the workflow release.yml, which does everything else.
Before
- Everything for the release is merged and CI is green on
main. - The OpenID conformance suite was run again against
main(conformance); it cannot run in CI. - The section Unreleased of CHANGELOG.md says what changed, in words an operator understands, and says clearly what breaks: a renamed or removed variable, a changed admin API, a change that needs action before updating.
Cut it
main is protected, so a release goes through a pull request and then a tag. Tags v* can only be created by administrators.
scripts/release.sh 0.2.0 # a branch release/0.2.0 with the changelog cut ("Unreleased" becomes "0.2.0 (date)"), pushed, as a pull request
# merge the pull request when its checks have passed, then
git checkout main && git pull
scripts/release.sh --tag 0.2.0 # tags the merged release commit as v0.2.0, whatever has been merged after it
git show v0.2.0 # look at what will go out
git push origin v0.2.0 # this starts the release
Taking it back: before merging, close the pull request and delete the branch; after tagging but before pushing, git tag -d v0.2.0.
What the workflow does
- Runs the checks of CI and the dependency audit again, for this commit.
- Refuses a tag that is not on
main, and one without an entry in the changelog. - Builds the image, starts it against a fresh Postgres (smoke test), starts the production example with it, and scans it for known vulnerabilities (high and critical findings that have a fix stop the release; see security).
- Builds it for amd64 and arm64 and pushes
ghcr.io/progwise/pfortewith the tags1.2.3,1.2,1andlatest(for0.x:0.1.0,0.1andlatest, but no0, which would move along through breaking releases), with an SBOM and a provenance attestation. - Starts the pushed image by its digest and runs the smoke test again, and checks that the arm64 image starts.
- Signs an attestation of where the image comes from, and creates the GitHub release with the changelog entry as its text.
The release is the last step: if anything fails before it, there is no release, and the workflow can be run again.
Rehearsing. Actions → Release → Run workflow with a version does the same without publishing anything: it builds both platforms, tests and scans, and takes the notes from "Unreleased" if the version has no entry yet.
Pre-releases. A tag such as v0.2.0-rc.1 makes a pre-release on GitHub. It gets no latest and no 0.2 tag.
After the first release
Check from outside that the image can be pulled without a login: docker logout ghcr.io && docker pull ghcr.io/progwise/pforte:0.1.0.
The workflow cannot tell, because it pulls with a login. A new package of an organisation can start as private; if it did, make it
public: the organisation's Packages → pforte → Package settings → Change visibility.
Verifying an image
gh attestation verify oci://ghcr.io/progwise/pforte:0.1.0 --owner progwise
docker buildx imagetools inspect ghcr.io/progwise/pforte:0.1.0 # platforms, SBOM and provenance
Versions
Semantic versioning. While the version starts with 0, a minor release (0.2.0) may break things and a patch
release (0.1.1) only fixes. From 1.0.0 a breaking change means a new major version. These count as breaking:
- an environment variable that is renamed, removed or changes its meaning (configuration),
- a change of the admin API that clients of it must follow (the OpenAPI document is the contract),
- a change in the claims or tokens that applications receive,
- a migration that needs action before it can run.
Migrations only go forward: there is no downgrade. That is why the update instructions say "back up first".
If something goes wrong
- The workflow fails before the release exists: if the cause was temporary, run the failed workflow again. A run uses the
workflow file of the commit that was tagged, so if the fix is in a workflow file, the tag has to move to a commit that has it:
git push origin :refs/tags/v0.1.0 && git tag -d v0.1.0(the tag protection lets only an administrator do this), fix onmainthrough a pull request, thengit tag -a v0.1.0 -m "pforte 0.1.0"and push the tag. This is fine only as long as no GitHub release exists and nobody has used the version. The tags a failed run already pushed to the registry are overwritten, except for tags that the fixed workflow no longer makes: delete those in the package's settings. - A published version is bad: do not reuse its number. Publish a fixed version, and say in its changelog entry what was wrong. A GitHub release can be edited to warn about the version; an image can be deleted in the package settings.