diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cd692f9..9c2af0e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,6 +6,9 @@ on: permissions: read-all +env: + IMAGE_NAME: phanan/koel + jobs: test: name: Run tests @@ -16,19 +19,39 @@ jobs: - name: Run tests uses: ./.github/actions/test - deploy: - name: Deploy to Docker Hub - runs-on: ubuntu-24.04 + build: + name: Build ${{ matrix.platform }} + runs-on: ${{ matrix.runner }} needs: [test] + strategy: + fail-fast: false + matrix: + include: + # amd64 and arm64 each build on their own architecture. arm/v7 is 32-bit ARM, + # which the 64-bit Arm runners cannot execute, so it stays emulated on x86. + - platform: linux/amd64 + runner: ubuntu-24.04 + emulated: false + - platform: linux/arm64 + runner: ubuntu-24.04-arm + emulated: false + - platform: linux/arm/v7 + runner: ubuntu-24.04 + emulated: true steps: - name: Checkout code uses: actions/checkout@v6 + - name: Name this platform + env: + PLATFORM: ${{ matrix.platform }} + run: echo "PLATFORM_SLUG=${PLATFORM//\//-}" >> "$GITHUB_ENV" + - name: Set up QEMU + if: matrix.emulated uses: docker/setup-qemu-action@v4 - id: qemu with: - platforms: linux/amd64,linux/arm64,linux/arm/v7 + platforms: ${{ matrix.platform }} - name: Set up Docker Build uses: docker/setup-buildx-action@v4 @@ -41,16 +64,76 @@ jobs: - name: Resolve version id: version - run: | - REF="$GITHUB_REF_NAME" - echo "VERSION=${REF#v}" >> "$GITHUB_OUTPUT" - echo "TAG=${REF}" >> "$GITHUB_OUTPUT" + run: echo "TAG=$GITHUB_REF_NAME" >> "$GITHUB_OUTPUT" + # Each platform is pushed as an untagged image, identified only by its digest. + # The merge job below collects the digests into one tagged multi-arch manifest. - name: Build and push the production image + id: build uses: docker/build-push-action@v7 with: - push: true - tags: phanan/koel:latest,phanan/koel:${{ steps.version.outputs.VERSION }} - platforms: linux/amd64,linux/arm64,linux/arm/v7 + platforms: ${{ matrix.platform }} build-args: | KOEL_VERSION_REF=${{ steps.version.outputs.TAG }} + outputs: type=image,name=${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true + + - name: Record the digest + env: + DIGEST: ${{ steps.build.outputs.digest }} + run: | + mkdir -p /tmp/digests + touch "/tmp/digests/${DIGEST#sha256:}" + + - name: Upload the digest + uses: actions/upload-artifact@v4 + with: + name: digest-${{ env.PLATFORM_SLUG }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + merge: + name: Push the multi-arch manifest + runs-on: ubuntu-24.04 + needs: [build] + steps: + - name: Download the digests + uses: actions/download-artifact@v4 + with: + path: /tmp/digests + pattern: digest-* + merge-multiple: true + + - name: Set up Docker Build + uses: docker/setup-buildx-action@v4 + + - name: Login to DockerHub + uses: docker/login-action@v4 + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_PASSWORD }} + + - name: Resolve version + id: version + run: echo "VERSION=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT" + + - name: Create the manifest + working-directory: /tmp/digests + env: + VERSION: ${{ steps.version.outputs.VERSION }} + run: | + sources=() + + for digest in *; do + sources+=("$IMAGE_NAME@sha256:$digest") + done + + docker buildx imagetools create \ + --tag "$IMAGE_NAME:latest" \ + --tag "$IMAGE_NAME:$VERSION" \ + "${sources[@]}" + + - name: Verify the manifest + env: + VERSION: ${{ steps.version.outputs.VERSION }} + run: docker buildx imagetools inspect "$IMAGE_NAME:$VERSION" diff --git a/AGENTS.md b/AGENTS.md index f545fa9..5677e17 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,6 +12,8 @@ This repo builds the official Docker image for [koel](https://github.com/koel/ko 5. Tags `vX.Y.Z` (lightweight) and force-moves the `latest` tag. 6. Pushes `master`, the new tag, and the force-updated `latest` tag. - The tag push triggers `.github/workflows/release.yml`: it runs goss tests, then (on success) builds a multi-arch image (`linux/amd64`, `linux/arm64`, `linux/arm/v7`) and pushes to Docker Hub as `phanan/koel:latest` and `phanan/koel:X.Y.Z` (note: `v` prefix stripped — see "Image tags" below). +- Each platform builds on its own runner and is pushed untagged, identified only by its digest; a final `merge` job stitches the digests into one tagged manifest with `docker buildx imagetools create`. So a partial failure leaves orphan digests on Docker Hub but never a half-built tag. +- `linux/amd64` builds on `ubuntu-24.04` and `linux/arm64` on `ubuntu-24.04-arm`, both native. `linux/arm/v7` is 32-bit ARM, which neither 64-bit runner can execute, so it alone still runs under QEMU and dominates the wall clock. Dropping it would make the whole release fast, at the cost of older Raspberry Pi support. - There is **no draft step** for Docker images. If the workflow succeeds, the image is live on Docker Hub immediately. If goss tests fail, the git tag is already public but no image is pushed — you'll need to investigate and re-tag. - Wait for the workflow with `gh run watch ` (workflow name: `Release Docker image`). Verify after with `docker pull phanan/koel:X.Y.Z`. - The Docker release for a given version should follow the koel app release for that same version. Run the app release first (`php artisan koel:release` in the koel repo), wait for it to publish on GitHub, then run `./release vX.Y.Z` here.