diff --git a/.gitea/workflows/build-and-publish.yaml b/.gitea/workflows/build-and-publish.yaml index 1ec040f..fa14cbd 100644 --- a/.gitea/workflows/build-and-publish.yaml +++ b/.gitea/workflows/build-and-publish.yaml @@ -45,7 +45,8 @@ jobs: # The suite runs inside the image, against the interpreter that ships, # rather than against whatever the runner happens to provide. A failing - # test fails the build. Layers are shared with the push build below. + # test fails the build. The runtime stage below is built from the same + # daemon afterwards, so its layers are already in cache. - name: Run the test suite inside the image run: docker build --target test -t music-curator:test . @@ -124,9 +125,6 @@ jobs: echo "release=${release}" >> "$GITHUB_OUTPUT" echo "Computed bump=${bump}, release=${release}, base=${base}" - - name: Set up Buildx - uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4 - - name: Log in to the Gitea container registry if: github.event_name != 'pull_request' uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4 @@ -135,21 +133,33 @@ jobs: username: ${{ github.repository_owner }} password: ${{ secrets.PACKAGES_TOKEN }} - - name: Build and push - uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7 - with: - context: . - # Without this the last stage in the Dockerfile -- the test stage -- - # would be what gets published. - target: runtime - # The NAS is the only host this runs on. Building arm64 as well would - # mean emulating it under QEMU for no consumer. - platforms: linux/amd64 - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.version.outputs.tags }} - labels: | - org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} - org.opencontainers.image.revision=${{ github.sha }} + # Plain `docker build` rather than buildx. buildx boots its own buildkit + # in a container with a cache of its own, so it shared nothing with the + # test build above and rebuilt the image from the base image up -- two + # full builds per run. It earns that cost when building for several + # platforms; this only ever targets the amd64 NAS, so it does not. + # + # `--target runtime` is a strict prefix of the test stage, so every layer + # is already in the daemon's cache and this resolves in seconds. + - name: Build the runtime image + run: | + set -euo pipefail + tags=() + while IFS= read -r tag; do + [ -n "$tag" ] && tags+=(-t "$tag") + done <<< "${{ steps.version.outputs.tags }}" + docker build --target runtime \ + --label "org.opencontainers.image.source=${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}" \ + --label "org.opencontainers.image.revision=${GITHUB_SHA}" \ + "${tags[@]}" . + + - name: Push + if: github.event_name != 'pull_request' + run: | + set -euo pipefail + while IFS= read -r tag; do + [ -n "$tag" ] && docker push "$tag" + done <<< "${{ steps.version.outputs.tags }}" # Record the release: write the computed version into pyproject.toml, then # commit and tag it, so the packaging metadata always matches the release