11on :
22 workflow_call :
33 inputs :
4- platforms :
5- required : true
6- type : string
74 cache :
85 type : boolean
96 default : true
10- use_native_arm64_builder :
11- type : boolean
127 push_to_images :
138 type : string
149 version_prerelease :
2419 file_to_build :
2520 type : string
2621
22+ # This builds multiple images with one runner each, allowing us to build for multiple architectures
23+ # using Github's runners.
24+ # The two-step process is adapted form:
25+ # https://docs.docker.com/build/ci/github-actions/multi-platform/#distribute-build-across-multiple-runners
2726jobs :
27+ # Build each (amd64 and arm64) image separately
2828 build-image :
29- runs-on : ubuntu-latest
29+ runs-on : ${{ startsWith(matrix.platform, 'linux/arm') && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
30+ strategy :
31+ fail-fast : false
32+ matrix :
33+ platform :
34+ - linux/amd64
35+ - linux/arm64
3036
3137 steps :
3238 - uses : actions/checkout@v4
3339
34- - uses : docker/setup-qemu-action@v3
35- if : contains(inputs.platforms, 'linux/arm64') && !inputs.use_native_arm64_builder
36-
37- - uses : docker/setup-buildx-action@v3
38- id : buildx
39- if : ${{ !(inputs.use_native_arm64_builder && contains(inputs.platforms, 'linux/arm64')) }}
40-
41- - name : Start a local Docker Builder
42- if : inputs.use_native_arm64_builder && contains(inputs.platforms, 'linux/arm64')
40+ - name : Prepare
41+ env :
42+ PUSH_TO_IMAGES : ${{ inputs.push_to_images }}
4343 run : |
44- docker run --rm -d --name buildkitd -p 1234:1234 --privileged moby/buildkit:latest --addr tcp://0.0.0.0:1234
44+ platform=${{ matrix.platform }}
45+ echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
46+ # Transform multi-line variable into comma-separated variable
47+ image_names=${PUSH_TO_IMAGES//$'\n'/,}
48+ echo "IMAGE_NAMES=${image_names%,}" >> $GITHUB_ENV
4549
4650 - uses : docker/setup-buildx-action@v3
47- id : buildx-native
48- if : inputs.use_native_arm64_builder && contains(inputs.platforms, 'linux/arm64')
49- with :
50- driver : remote
51- endpoint : tcp://localhost:1234
52- platforms : linux/amd64
53- append : |
54- - endpoint: tcp://${{ vars.DOCKER_BUILDER_HETZNER_ARM64_01_HOST }}:13865
55- platforms: linux/arm64
56- name: mastodon-docker-builder-arm64-01
57- driver-opts:
58- - servername=mastodon-docker-builder-arm64-01
59- env :
60- BUILDER_NODE_1_AUTH_TLS_CACERT : ${{ secrets.DOCKER_BUILDER_HETZNER_ARM64_01_CACERT }}
61- BUILDER_NODE_1_AUTH_TLS_CERT : ${{ secrets.DOCKER_BUILDER_HETZNER_ARM64_01_CERT }}
62- BUILDER_NODE_1_AUTH_TLS_KEY : ${{ secrets.DOCKER_BUILDER_HETZNER_ARM64_01_KEY }}
51+ id : buildx
6352
6453 - name : Log in to Docker Hub
6554 if : contains(inputs.push_to_images, 'tootsuite')
@@ -76,28 +65,106 @@ jobs:
7665 username : ${{ github.actor }}
7766 password : ${{ secrets.GITHUB_TOKEN }}
7867
79- - uses : docker/metadata-action@v5
68+ - name : Docker meta
8069 id : meta
70+ uses : docker/metadata-action@v5
8171 if : ${{ inputs.push_to_images != '' }}
8272 with :
8373 images : ${{ inputs.push_to_images }}
8474 flavor : ${{ inputs.flavor }}
85- tags : ${{ inputs.tags }}
8675 labels : ${{ inputs.labels }}
8776
88- - uses : docker/build-push-action@v6
77+ - name : Build and push by digest
78+ id : build
79+ uses : docker/build-push-action@v6
8980 with :
9081 context : .
9182 file : ${{ inputs.file_to_build }}
9283 build-args : |
9384 MASTODON_VERSION_PRERELEASE=${{ inputs.version_prerelease }}
9485 MASTODON_VERSION_METADATA=${{ inputs.version_metadata }}
9586 SOURCE_COMMIT=${{ github.sha }}
96- platforms : ${{ inputs.platforms }}
87+ platforms : ${{ matrix.platform }}
9788 provenance : false
98- builder : ${{ steps.buildx.outputs.name || steps.buildx-native.outputs.name }}
9989 push : ${{ inputs.push_to_images != '' }}
100- tags : ${{ steps.meta.outputs.tags }}
101- labels : ${{ steps.meta.outputs.labels }}
10290 cache-from : ${{ inputs.cache && 'type=gha' || '' }}
10391 cache-to : ${{ inputs.cache && 'type=gha,mode=max' || '' }}
92+ outputs : type=image,"name=${{ env.IMAGE_NAMES }}",push-by-digest=true,name-canonical=true,push=${{ inputs.push_to_images != '' }}
93+
94+ - name : Export digest
95+ if : ${{ inputs.push_to_images != '' }}
96+ run : |
97+ mkdir -p "${{ runner.temp }}/digests"
98+ digest="${{ steps.build.outputs.digest }}"
99+ touch "${{ runner.temp }}/digests/${digest#sha256:}"
100+
101+ - name : Upload digest
102+ if : ${{ inputs.push_to_images != '' }}
103+ uses : actions/upload-artifact@v4
104+ with :
105+ # `hashFiles` is used to disambiguate between streaming and non-streaming images
106+ name : digests-${{ hashFiles(inputs.file_to_build) }}-${{ env.PLATFORM_PAIR }}
107+ path : ${{ runner.temp }}/digests/*
108+ if-no-files-found : error
109+ retention-days : 1
110+
111+ # Then merge the docker images into a single one
112+ merge-images :
113+ if : ${{ inputs.push_to_images != '' }}
114+ runs-on : ubuntu-24.04
115+ needs :
116+ - build-image
117+
118+ env :
119+ PUSH_TO_IMAGES : ${{ inputs.push_to_images }}
120+
121+ steps :
122+ - uses : actions/checkout@v4
123+
124+ - name : Download digests
125+ uses : actions/download-artifact@v4
126+ with :
127+ path : ${{ runner.temp }}/digests
128+ # `hashFiles` is used to disambiguate between streaming and non-streaming images
129+ pattern : digests-${{ hashFiles(inputs.file_to_build) }}-*
130+ merge-multiple : true
131+
132+ - name : Log in to Docker Hub
133+ if : contains(inputs.push_to_images, 'tootsuite')
134+ uses : docker/login-action@v3
135+ with :
136+ username : ${{ secrets.DOCKERHUB_USERNAME }}
137+ password : ${{ secrets.DOCKERHUB_TOKEN }}
138+
139+ - name : Log in to the GitHub Container registry
140+ if : contains(inputs.push_to_images, 'ghcr.io')
141+ uses : docker/login-action@v3
142+ with :
143+ registry : ghcr.io
144+ username : ${{ github.actor }}
145+ password : ${{ secrets.GITHUB_TOKEN }}
146+
147+ - name : Set up Docker Buildx
148+ uses : docker/setup-buildx-action@v3
149+
150+ - name : Docker meta
151+ id : meta
152+ uses : docker/metadata-action@v5
153+ if : ${{ inputs.push_to_images != '' }}
154+ with :
155+ images : ${{ inputs.push_to_images }}
156+ flavor : ${{ inputs.flavor }}
157+ tags : ${{ inputs.tags }}
158+ labels : ${{ inputs.labels }}
159+
160+ - name : Create manifest list and push
161+ working-directory : ${{ runner.temp }}/digests
162+ run : |
163+ echo "$PUSH_TO_IMAGES" | xargs -I{} \
164+ docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
165+ $(printf '{}@sha256:%s ' *)
166+
167+ - name : Inspect image
168+ run : |
169+ echo "$PUSH_TO_IMAGES" | xargs -i{} \
170+ docker buildx imagetools inspect {}:${{ steps.meta.outputs.version }}
0 commit comments