Skip to content

Commit 9bb9360

Browse files
authored
build each image platform on its own runner, natively where possible (#228)
arm64 was emulated under QEMU on an x86 runner, which is where most of the release time went. GitHub's arm64 runners are free for public repos, so amd64 and arm64 now each build on their own architecture. Platforms build in parallel and push untagged by digest; a merge job assembles them into the tagged manifest. arm/v7 is 32-bit ARM that the 64-bit runners cannot execute, so it stays emulated.
1 parent 6e58cfc commit 9bb9360

2 files changed

Lines changed: 97 additions & 12 deletions

File tree

.github/workflows/release.yml

Lines changed: 95 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ on:
66

77
permissions: read-all
88

9+
env:
10+
IMAGE_NAME: phanan/koel
11+
912
jobs:
1013
test:
1114
name: Run tests
@@ -16,19 +19,39 @@ jobs:
1619
- name: Run tests
1720
uses: ./.github/actions/test
1821

19-
deploy:
20-
name: Deploy to Docker Hub
21-
runs-on: ubuntu-24.04
22+
build:
23+
name: Build ${{ matrix.platform }}
24+
runs-on: ${{ matrix.runner }}
2225
needs: [test]
26+
strategy:
27+
fail-fast: false
28+
matrix:
29+
include:
30+
# amd64 and arm64 each build on their own architecture. arm/v7 is 32-bit ARM,
31+
# which the 64-bit Arm runners cannot execute, so it stays emulated on x86.
32+
- platform: linux/amd64
33+
runner: ubuntu-24.04
34+
emulated: false
35+
- platform: linux/arm64
36+
runner: ubuntu-24.04-arm
37+
emulated: false
38+
- platform: linux/arm/v7
39+
runner: ubuntu-24.04
40+
emulated: true
2341
steps:
2442
- name: Checkout code
2543
uses: actions/checkout@v6
2644

45+
- name: Name this platform
46+
env:
47+
PLATFORM: ${{ matrix.platform }}
48+
run: echo "PLATFORM_SLUG=${PLATFORM//\//-}" >> "$GITHUB_ENV"
49+
2750
- name: Set up QEMU
51+
if: matrix.emulated
2852
uses: docker/setup-qemu-action@v4
29-
id: qemu
3053
with:
31-
platforms: linux/amd64,linux/arm64,linux/arm/v7
54+
platforms: ${{ matrix.platform }}
3255

3356
- name: Set up Docker Build
3457
uses: docker/setup-buildx-action@v4
@@ -41,16 +64,76 @@ jobs:
4164

4265
- name: Resolve version
4366
id: version
44-
run: |
45-
REF="$GITHUB_REF_NAME"
46-
echo "VERSION=${REF#v}" >> "$GITHUB_OUTPUT"
47-
echo "TAG=${REF}" >> "$GITHUB_OUTPUT"
67+
run: echo "TAG=$GITHUB_REF_NAME" >> "$GITHUB_OUTPUT"
4868

69+
# Each platform is pushed as an untagged image, identified only by its digest.
70+
# The merge job below collects the digests into one tagged multi-arch manifest.
4971
- name: Build and push the production image
72+
id: build
5073
uses: docker/build-push-action@v7
5174
with:
52-
push: true
53-
tags: phanan/koel:latest,phanan/koel:${{ steps.version.outputs.VERSION }}
54-
platforms: linux/amd64,linux/arm64,linux/arm/v7
75+
platforms: ${{ matrix.platform }}
5576
build-args: |
5677
KOEL_VERSION_REF=${{ steps.version.outputs.TAG }}
78+
outputs: type=image,name=${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
79+
80+
- name: Record the digest
81+
env:
82+
DIGEST: ${{ steps.build.outputs.digest }}
83+
run: |
84+
mkdir -p /tmp/digests
85+
touch "/tmp/digests/${DIGEST#sha256:}"
86+
87+
- name: Upload the digest
88+
uses: actions/upload-artifact@v4
89+
with:
90+
name: digest-${{ env.PLATFORM_SLUG }}
91+
path: /tmp/digests/*
92+
if-no-files-found: error
93+
retention-days: 1
94+
95+
merge:
96+
name: Push the multi-arch manifest
97+
runs-on: ubuntu-24.04
98+
needs: [build]
99+
steps:
100+
- name: Download the digests
101+
uses: actions/download-artifact@v4
102+
with:
103+
path: /tmp/digests
104+
pattern: digest-*
105+
merge-multiple: true
106+
107+
- name: Set up Docker Build
108+
uses: docker/setup-buildx-action@v4
109+
110+
- name: Login to DockerHub
111+
uses: docker/login-action@v4
112+
with:
113+
username: ${{ secrets.DOCKER_HUB_USERNAME }}
114+
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
115+
116+
- name: Resolve version
117+
id: version
118+
run: echo "VERSION=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
119+
120+
- name: Create the manifest
121+
working-directory: /tmp/digests
122+
env:
123+
VERSION: ${{ steps.version.outputs.VERSION }}
124+
run: |
125+
sources=()
126+
127+
for digest in *; do
128+
sources+=("$IMAGE_NAME@sha256:$digest")
129+
done
130+
131+
docker buildx imagetools create \
132+
--tag "$IMAGE_NAME:latest" \
133+
--tag "$IMAGE_NAME:$VERSION" \
134+
"${sources[@]}"
135+
136+
- name: Verify the manifest
137+
env:
138+
VERSION: ${{ steps.version.outputs.VERSION }}
139+
run: docker buildx imagetools inspect "$IMAGE_NAME:$VERSION"

AGENTS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ This repo builds the official Docker image for [koel](https://github.com/koel/ko
1212
5. Tags `vX.Y.Z` (lightweight) and force-moves the `latest` tag.
1313
6. Pushes `master`, the new tag, and the force-updated `latest` tag.
1414
- 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).
15+
- 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.
16+
- `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.
1517
- 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.
1618
- Wait for the workflow with `gh run watch <id>` (workflow name: `Release Docker image`). Verify after with `docker pull phanan/koel:X.Y.Z`.
1719
- 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.

0 commit comments

Comments
 (0)