release: cut a GitHub release for each image push
Assisted-by: Claude Opus 5 via Claude Code
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 07a499b..4d1e027 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -26,7 +26,7 @@ jobs:
release:
runs-on: ubuntu-latest
permissions:
- contents: read # checkout.
+ contents: write # checkout, and cut the release.
packages: write # push image to GHCR.
steps:
@@ -135,3 +135,37 @@ jobs:
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
+
+ - name: Create GitHub release
+ env:
+ GH_TOKEN: ${{ github.token }}
+ STEPS_VERSIONS_OUTPUTS_CADDY: ${{ steps.versions.outputs.caddy }}
+ STEPS_VERSIONS_OUTPUTS_CALVER: ${{ steps.versions.outputs.calver }}
+ STEPS_VERSIONS_OUTPUTS_TAILSCALE: ${{ steps.versions.outputs.tailscale }}
+ STEPS_VERSIONS_OUTPUTS_RATELIMIT: ${{ steps.versions.outputs.ratelimit }}
+ STEPS_META_OUTPUTS_TAGS: ${{ steps.meta.outputs.tags }}
+ run: |
+ set -euo pipefail
+
+ tag="v$STEPS_VERSIONS_OUTPUTS_CADDY-$STEPS_VERSIONS_OUTPUTS_CALVER"
+
+ # Indented code blocks instead of fences: backticks in --notes-file are
+ # fine, but these notes also go through echo.
+ {
+ echo "| Component | Version |"
+ echo "| --- | --- |"
+ echo "| Caddy | $STEPS_VERSIONS_OUTPUTS_CADDY |"
+ echo "| caddy-tailscale | $STEPS_VERSIONS_OUTPUTS_TAILSCALE |"
+ echo "| caddy-ratelimit | $STEPS_VERSIONS_OUTPUTS_RATELIMIT |"
+ echo
+ echo "Images pushed:"
+ echo
+ sed 's/^/ /' <<< "$STEPS_META_OUTPUTS_TAGS"
+ } > notes.md
+
+ # A same-day rebuild reuses the image tag, so reuse the release too.
+ if gh release view "$tag" > /dev/null 2>&1; then
+ gh release edit "$tag" --notes-file notes.md
+ else
+ gh release create "$tag" --target "$GITHUB_SHA" --notes-file notes.md
+ fi
diff --git a/README.md b/README.md
index 26d3e60..6647665 100644
--- a/README.md
+++ b/README.md
@@ -22,3 +22,7 @@ just release [caddy] [tailscale] [ratelimit]
Blank versions default to the latest. A plugin version can be a tag, a commit
hash, or a branch. The run summary and image labels record the plugin versions
that went into the build.
+
+A successful run also cuts a GitHub release tagged `v<caddy>-<yyyy.mm.dd>`,
+like `v2.11.4-2026.09.14`, with the same versions and the image tags it
+pushed.