1+ # https://github.com/sredevopsorg/multi-arch-docker-github-workflow
2+ # This workflow builds a multi-arch Docker image using GitHub Actions and separated Github Runners with native support for ARM64 and AMD64 architectures, without using QEMU emulation.
3+ # It uses Docker Buildx to build and push the image to GitHub Container Registry (GHCR).
4+ name : Build multi arch Docker Image with separate Github Runners
5+
6+ on :
7+ workflow_dispatch :
8+ push :
9+ branches :
10+ - master
11+ tags :
12+ - ' v*'
13+ release :
14+ types : [published]
15+ env :
16+ # The name of the Docker image to be built and pushed to GHCR
17+ # The image name is derived from the GitHub repository name and the GitHub Container Registry (GHCR) URL.
18+ # The image name will be in the format: ghcr.io/<owner>/<repo>
19+ GHCR_IMAGE : ghcr.io/ascheret/prism
20+
21+ permissions :
22+ # Global permissions for the workflow, which can be overridden at the job level
23+ contents : read
24+
25+ concurrency :
26+ # This concurrency group ensures that only one job in the group runs at a time.
27+ # If a new job is triggered, the previous one will be canceled.
28+ group : ${{ github.workflow }}-${{ github.ref }}
29+ cancel-in-progress : true
30+
31+ jobs :
32+ # The build job builds the Docker image for each platform specified in the matrix.
33+ build :
34+ strategy :
35+ fail-fast : false
36+ matrix :
37+ platform :
38+ - linux/amd64
39+ - linux/arm64
40+ # The matrix includes two platforms: linux/amd64 and linux/arm64.
41+ # The build job will run for each platform in the matrix.
42+
43+ permissions :
44+ # Permissions for the build job, which can be overridden at the step level
45+ # The permissions are set to allow the job to write to the GitHub Container Registry (GHCR) and read from the repository.
46+ attestations : write
47+ actions : read
48+ checks : write
49+ contents : write
50+ deployments : none
51+ id-token : write
52+ issues : read
53+ discussions : read
54+ packages : write
55+ pages : none
56+ pull-requests : read
57+ repository-projects : read
58+ security-events : read
59+ statuses : read
60+
61+ runs-on : ${{ matrix.platform == 'linux/amd64' && 'ubuntu-latest' || matrix.platform == 'linux/arm64' && 'ubuntu-24.04-arm' }}
62+ # The job runs on different runners based on the platform.
63+ # For linux/amd64, it runs on the latest Ubuntu runner.
64+ # For linux/arm64, it runs on an Ubuntu 24.04 ARM runner.
65+ # The runner is selected based on the platform specified in the matrix.
66+
67+ name : Build Docker image for ${{ matrix.platform }}
68+
69+ steps :
70+ -
71+ name : Prepare environment for current platform
72+ # This step sets up the environment for the current platform being built.
73+ # It replaces the '/' character in the platform name with '-' and sets it as an environment variable.
74+ # This is useful for naming artifacts and other resources that cannot contain '/'.
75+ # The environment variable PLATFORMS_PAIR will be used later in the workflow.
76+ id : prepare
77+ run : |
78+ platform=${{ matrix.platform }}
79+ echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
80+
81+ - name : Checkout
82+ uses : actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
83+ # This step checks out the code from the repository.
84+ # It uses the actions/checkout action to clone the repository into the runner's workspace.
85+
86+ - name : Docker meta default
87+ # This step generates metadata for the Docker image.
88+ # It uses the docker/metadata-action to create metadata based on the repository information.
89+ # The metadata includes information such as the image name, tags, and labels.
90+ # The metadata will be used later in the workflow to build and push the Docker image.
91+ id : meta
92+ uses : docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
93+ with :
94+ images : ${{ env.GHCR_IMAGE }}
95+
96+ - name : Set up Docker Context for Buildx
97+ # This step sets up a Docker context for Buildx.
98+ # It creates a new context named "builders" that will be used for building the Docker image.
99+ # The context allows Buildx to use the Docker daemon for building images.
100+ id : buildx-context
101+ run : |
102+ docker context create builders
103+
104+ - name : Set up Docker Buildx
105+ # This step sets up Docker Buildx, which is a Docker CLI plugin for extended build capabilities with BuildKit.
106+ # It uses the docker/setup-buildx-action to configure Buildx with the specified context and platforms.
107+ # The platforms are specified in the matrix and will be used for building the Docker image.
108+ uses : docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
109+ with :
110+ endpoint : builders
111+ platforms : ${{ matrix.platform }}
112+
113+ - name : Login to GitHub Container Registry
114+ # This step logs in to the GitHub Container Registry (GHCR) using the docker/login-action.
115+ # It uses the GitHub actor's username and the GITHUB_TOKEN secret for authentication.
116+ uses : docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
117+ with :
118+ registry : ghcr.io
119+ username : ${{ github.actor }}
120+ password : ${{ secrets.GITHUB_TOKEN }}
121+
122+ - name : Build and push by digest
123+ # This step builds and pushes the Docker image using Buildx.
124+ # It uses the docker/build-push-action to build the image with the specified context and platforms.
125+ # The image is built with the labels and annotations generated in the previous steps.
126+ # The outputs are configured to push the image by digest, which allows for better caching and versioning.
127+ # The cache-from and cache-to options are used to enable caching for the build process.
128+ # The cache is stored in GitHub Actions cache and is scoped to the repository, branch, and platform.
129+ id : build
130+ uses : docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0
131+ env :
132+ DOCKER_BUILDKIT : 1
133+ with :
134+ context : .
135+ platforms : ${{ matrix.platform }}
136+ labels : ${{ steps.meta.outputs.labels }}
137+ annotations : ${{ steps.meta.outputs.annotations }}
138+ outputs : type=image,name=${{ env.GHCR_IMAGE }},push-by-digest=true,name-canonical=true,push=true,oci-mediatypes=true
139+ cache-from : type=gha,scope=${{ github.repository }}-${{ github.ref_name }}-${{ matrix.platform }}
140+ cache-to : type=gha,scope=${{ github.repository }}-${{ github.ref_name }}-${{ matrix.platform }}
141+ build-args : |
142+ BUILD_TYPE=production
143+
144+
145+ - name : Export digest
146+ # This step exports the digest of the built image to a file.
147+ # It creates a directory in /tmp/digests and saves the digest of the image to a file.
148+ # The digest is obtained from the output of the build step.
149+ # The digest is used to uniquely identify the built image and can be used for further processing or verification.
150+ run : |
151+ mkdir -p /tmp/digests
152+ digest="${{ steps.build.outputs.digest }}"
153+ touch "/tmp/digests/${digest#sha256:}"
154+
155+ - name : Upload digest
156+ # This step uploads the digest file to the GitHub Actions artifact storage.
157+ # It uses the actions/upload-artifact action to upload the file created in the previous step.
158+ # The artifact is named digests-${{ env.PLATFORM_PAIR }}, where PLATFORM_PAIR is the platform name with '/' replaced by '-'.
159+ # The artifact is retained for 1 day, and if no files are found, it will throw an error.
160+ uses : actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
161+ with :
162+ name : digests-${{ env.PLATFORM_PAIR }}
163+ path : /tmp/digests/*
164+ if-no-files-found : error
165+ retention-days : 1
166+
167+
168+ merge :
169+ # This job merges the Docker manifests for the different platforms built in the previous job.
170+ name : Merge Docker manifests
171+ runs-on : ubuntu-latest
172+ permissions :
173+ attestations : write
174+ actions : read
175+ checks : read
176+ contents : read
177+ deployments : none
178+ id-token : write
179+ issues : read
180+ discussions : read
181+ packages : write
182+ pages : none
183+ pull-requests : read
184+ repository-projects : read
185+ security-events : read
186+ statuses : read
187+
188+ needs :
189+ - build
190+ # This job depends on the build job to complete before it starts.
191+ # It ensures that the Docker images for all platforms are built before merging the manifests.
192+ steps :
193+ - name : Download digests
194+ # This step downloads the digest files uploaded in the build job.
195+ # It uses the actions/download-artifact action to download the artifacts with the pattern digests-*.
196+ # The downloaded files are merged into the /tmp/digests directory.
197+ uses : actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
198+ with :
199+ path : /tmp/digests
200+ pattern : digests-*
201+ merge-multiple : true
202+
203+
204+ - name : Docker meta
205+ # This step generates metadata for the Docker image.
206+ # It uses the docker/metadata-action to create metadata based on the repository information.
207+ # The metadata includes information such as the image name, tags, and labels.
208+ id : meta
209+ uses : docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
210+ with :
211+ images : ${{ env.GHCR_IMAGE }}
212+ annotations : |
213+ type=org.opencontainers.image.description,value=${{ github.event.repository.description || 'No description provided' }}
214+ tags : |
215+ type=raw,value=latest,enable={{is_default_branch}}
216+ type=semver,pattern={{version}}
217+ type=semver,pattern={{major}}.{{minor}}
218+ type=sha,prefix=,format=long
219+
220+ - name : Set up Docker Buildx
221+ uses : docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
222+ # This step sets up Docker Buildx, which is a Docker CLI plugin for extended build capabilities with BuildKit.
223+ with :
224+ driver-opts : |
225+ network=host
226+
227+ - name : Login to GitHub Container Registry
228+ uses : docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
229+ # This step logs in to the GitHub Container Registry (GHCR) using the docker/login-action.
230+ # It uses the GitHub actor's username and the GITHUB_TOKEN secret for authentication.
231+ # The login is necessary to push the merged manifest list to GHCR.
232+ with :
233+ registry : ghcr.io
234+ username : ${{ github.actor }}
235+ password : ${{ secrets.GITHUB_TOKEN }}
236+
237+ - name : Get execution timestamp with RFC3339 format
238+ # This step gets the current execution timestamp in RFC3339 format.
239+ # It uses the date command to get the current UTC time and formats it as a string.
240+ # The timestamp is used for annotating the Docker manifest list.
241+ id : timestamp
242+ run : |
243+ echo "timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")" >> $GITHUB_OUTPUT
244+
245+ - name : Create manifest list and pushs
246+ # This step creates a manifest list for the Docker images built for different platforms.
247+ # It uses the docker buildx imagetools create command to create the manifest list.
248+ # The manifest list is annotated with metadata such as description, creation timestamp, and source URL.
249+ # The annotations are obtained from the metadata generated in the previous steps.
250+ # The manifest list is pushed to the GitHub Container Registry (GHCR) with the specified tags.
251+ working-directory : /tmp/digests
252+ id : manifest-annotate
253+ continue-on-error : true
254+ run : |
255+ docker buildx imagetools create \
256+ $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
257+ --annotation='index:org.opencontainers.image.description=${{ github.event.repository.description }}' \
258+ --annotation='index:org.opencontainers.image.created=${{ steps.timestamp.outputs.timestamp }}' \
259+ --annotation='index:org.opencontainers.image.url=${{ github.event.repository.url }}' \
260+ --annotation='index:org.opencontainers.image.source=${{ github.event.repository.url }}' \
261+ $(printf '${{ env.GHCR_IMAGE }}@sha256:%s ' *)
262+
263+ - name : Create manifest list and push without annotations
264+ # This step creates a manifest list for the Docker images built for different platforms.
265+ # It uses the docker buildx imagetools create command to create the manifest list.
266+ # The manifest list is created without annotations if the previous step fails.
267+ # The manifest list is pushed to the GitHub Container Registry (GHCR) with the specified tags.
268+ if : steps.manifest-annotate.outcome == 'failure'
269+ working-directory : /tmp/digests
270+ run : |
271+ docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
272+ $(printf '${{ env.GHCR_IMAGE }}@sha256:%s ' *)
273+
274+ - name : Inspect image
275+ # This step inspects the created manifest list to verify its contents.
276+ # It uses the docker buildx imagetools inspect command to display information about the manifest list.
277+ # The inspection output will show the platforms and tags associated with the manifest list.
278+ id : inspect
279+ run : |
280+ docker buildx imagetools inspect '${{ env.GHCR_IMAGE }}:${{ steps.meta.outputs.version }}'
0 commit comments