From 94f9a7349cb8c4bd2703005fa82c48fd74654cd7 Mon Sep 17 00:00:00 2001 From: Paul Burns Date: Fri, 7 Aug 2026 11:41:58 -0400 Subject: [PATCH 1/2] Publish Dependabot-orderable release tags from main builds Main builds that change the image or how it is built (Dockerfile, tool-versions.env, image/, scripts/build-image.sh) now mint a release tag like debian-ghc-9.10.3-build-2026.8.6.347 as a digest-identical re-tag of the sha manifest. Downstream repos pin these and receive bump PRs from Dependabot; sha tags remain for trying branch builds. The build version leads with the date because dependabot-core classifies bare numbers 1000-2999 as a different tag format than 999 and stops proposing updates (dependabot-core#11198). The date is the commit's rather than the wall clock's so that re-running an old main workflow recreates its original tag instead of minting one that sorts above newer releases while pointing at an older image. Co-Authored-By: Claude Fable 5 --- .github/workflows/build.yaml | 21 +++++++++++++ README.md | 57 +++++++++++++++++++++++++++++++++--- scripts/build-image.sh | 28 +++++++++++++++++- 3 files changed, 101 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index ce22ed9..bc583fe 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -113,8 +113,13 @@ jobs: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} + # Full history so the release-tag step can diff against the + # before-sha of the push; the repo is small enough for this to be + # cheap. - name: Checkout uses: actions/checkout@v6 + with: + fetch-depth: 0 - name: Set up Docker buildx uses: docker/setup-buildx-action@v4 @@ -164,3 +169,19 @@ jobs: - name: Build and push manifest run: | ./scripts/build-image.sh push-manifest + + # A release tag is what downstream Dependabot configs watch. Mint one + # only for main builds that changed the image or how it is built, so + # docs/CI-only merges don't open no-op bump PRs in every downstream + # repo. An unknown before-sha (force push, newly created branch) errs + # on the side of releasing. + - name: Publish release tag + if: ${{ github.ref == 'refs/heads/main' }} + run: | + before='${{ github.event.before }}' + if git cat-file -e "$before" 2>/dev/null && + ! git diff --name-only "$before" HEAD | grep -qE '^(Dockerfile|tool-versions\.env|image/|scripts/build-image\.sh)'; then + echo "No image-affecting changes since $before; skipping release tag" + else + ./scripts/build-image.sh push-release-tag + fi diff --git a/README.md b/README.md index eea0f2c..794fa03 100644 --- a/README.md +++ b/README.md @@ -84,14 +84,63 @@ and dhi.io logins fail on every Dependabot PR. The same two secrets also let Dependabot authenticate to dhi.io for base image digest updates (see `.github/dependabot.yaml`). -# How to build this for release +# Image tags and releases Once you push to GitHub (either on a branch or main), the GitHub workflow will build a multi-architecture version of the image and publish it to the -GitHub Container Registry. From there it can be used as a base for other +GitHub Container Registry. From there it can be used as a base for other images or directly in projects that require no further tools to be installed. +The registry holds three kinds of tags: + +- `debian-ghc-X.Y.Z-` — per-commit tags, published for every push on + every branch. Use these to try out a not-yet-merged image. +- `debian-ghc-X.Y.Z-build-YYYY.M.D.N` — release tags, minted automatically + from main whenever a push changed the image or how it is built + (`Dockerfile`, `tool-versions.env`, `image/`, or + `scripts/build-image.sh`). Docs- and CI-only merges don't mint one. + These are the tags downstream repositories should pin. The date is the + commit date and `N` is the workflow run number; month and day are + unpadded (`2026.8.6`, not `2026.08.06`) because Dependabot compares + version segments numerically. +- `buildcache-*` — registry-hosted layer caches, internal to CI; never pin + these. + +A release tag is a digest-identical re-tag of the same commit's sha +manifest, created after the Trivy scan (so a blocking scan configuration +also blocks releases). The tag is derived from the commit date and run +number, so re-running a main workflow recreates the same tag rather than +minting a new one. + +To try a candidate image downstream before merging: push your haskell-tools +branch, pin the resulting `debian-ghc-X.Y.Z-` tag on a branch of the +downstream repository, and iterate. Once your change merges here, Dependabot +opens the release-tag bump PR in each downstream repository — discard the +sha-tag test pin rather than merging it. + # For Flipstone Developers -Update all our repositories that use this image, to the latest, when -a new image is published. This list can be found in the codex. +Repositories that use this image should pin a release tag +(`debian-ghc-X.Y.Z-build-YYYY.M.D.N`) and carry a `.github/dependabot.yml` +so new releases arrive as bump PRs automatically: + +```yaml +version: 2 +updates: + - package-ecosystem: "docker-compose" # image: lines in compose files + directory: "/" + schedule: + interval: "weekly" + - package-ecosystem: "docker" # FROM lines in Dockerfiles + directory: "/" + schedule: + interval: "weekly" +``` + +Dependabot keeps `tag@sha256:...` pins working too — it updates the tag and +digest together. It only proposes updates within the currently pinned GHC +version: the GHC version sits in the part of the tag Dependabot treats as an +opaque prefix, so a GHC upgrade is a deliberate, one-time manual pin edit in +each downstream repository, made alongside the code and resolver changes the +upgrade requires anyway. The list of repositories using this image can be +found in the codex. diff --git a/scripts/build-image.sh b/scripts/build-image.sh index ccddccb..bc1fd68 100755 --- a/scripts/build-image.sh +++ b/scripts/build-image.sh @@ -89,6 +89,32 @@ case $COMMAND in docker buildx imagetools create --tag "$TAG_ROOT" "$AMD_TAG" "$ARM_TAG" ;; + push-release-tag) + set_tag_and_arch_variables + if [ -z "$GITHUB_RUN_NUMBER" ]; then + echo "GITHUB_RUN_NUMBER must be set (this command is meant to run in CI)" + exit 1 + fi + if [ "$COMMIT_SHA" = "uncommitted" ]; then + echo "Refusing to publish a release tag from a dirty tree" + exit 1 + fi + # Release tags are what downstream Dependabot configs watch, so the + # version must stay within a single dependabot-core tag format class. + # A bare run number breaks at 1000 (dependabot-core#11198); leading + # with the 4-digit year avoids that for good. Month and day are + # unpadded on purpose: Dependabot compares segments numerically. + # + # The date is the commit's, not today's: a wall-clock date would let a + # re-run of an old workflow mint a tag that sorts above newer releases + # while pointing at an older image. With the commit date, a re-run + # recreates the identical tag. + COMMIT_DATE=$(TZ=UTC git show -s --format=%cd --date=format-local:%Y.%-m.%-d HEAD) + RELEASE_TAG="ghcr.io/flipstone/haskell-tools:debian-ghc-$GHC_VERSION-build-$COMMIT_DATE.$GITHUB_RUN_NUMBER" + echo "Publishing release tag $RELEASE_TAG (re-tag of $TAG_ROOT)" + docker buildx imagetools create --tag "$RELEASE_TAG" "$TAG_ROOT" + ;; + scan-local-beta) mkdir -p trivy-reports docker compose run --rm trivy image haskell-tools-beta | tee trivy-reports/haskell-tools-beta.txt @@ -101,6 +127,6 @@ case $COMMAND in docker compose run --rm trivy image "$AMD_TAG" | tee trivy-reports/amd64.txt ;; *) - echo "usage: ./scripts/build-image.sh build-local-beta|build-arch-tag|build-and-push-arch-tag|push-manifest|scan-local-beta|scan-amd64-tag" + echo "usage: ./scripts/build-image.sh build-local-beta|build-arch-tag|build-and-push-arch-tag|push-manifest|push-release-tag|scan-local-beta|scan-amd64-tag" exit 1 esac; From d5516833cd81007384bc03ce1ced9cf278a57161 Mon Sep 17 00:00:00 2001 From: Paul Burns <1413314+onslaughtq@users.noreply.github.com> Date: Fri, 7 Aug 2026 12:04:02 -0400 Subject: [PATCH 2/2] Update README to use yaml instead of yml Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 794fa03..714f421 100644 --- a/README.md +++ b/README.md @@ -121,7 +121,7 @@ sha-tag test pin rather than merging it. # For Flipstone Developers Repositories that use this image should pin a release tag -(`debian-ghc-X.Y.Z-build-YYYY.M.D.N`) and carry a `.github/dependabot.yml` +(`debian-ghc-X.Y.Z-build-YYYY.M.D.N`) and carry a `.github/dependabot.yaml` so new releases arrive as bump PRs automatically: ```yaml