Skip to content

Commit 00aecfd

Browse files
committed
ci: add workflow to build and publish docker image to GHCR
1 parent 636382f commit 00aecfd

File tree

3 files changed

+283
-3
lines changed

3 files changed

+283
-3
lines changed

.github/workflows/cd-docker.yml

Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
# 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.
2+
# It uses Docker Buildx to build and push the image to GitHub Container Registry (GHCR).
3+
name: CD Build and publish multi arch Docker Image
4+
5+
on:
6+
workflow_dispatch:
7+
workflow_run:
8+
workflows:
9+
- Continuous Delivery
10+
types:
11+
- completed
12+
branches:
13+
- main
14+
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/${{ github.repository }}
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+
include:
38+
- platform: linux/amd64
39+
platform_pair: linux-amd64
40+
- platform: linux/arm64
41+
platform_pair: linux-arm64
42+
# The matrix includes two platforms: linux/amd64 and linux/arm64.
43+
# The build job will run for each platform in the matrix.
44+
45+
permissions:
46+
# Permissions for the build job, which can be overridden at the step level
47+
# The permissions are set to allow the job to write to the GitHub Container Registry (GHCR) and read from the repository.
48+
attestations: write
49+
actions: read
50+
checks: write
51+
contents: write
52+
deployments: none
53+
id-token: write
54+
issues: read
55+
discussions: read
56+
packages: write
57+
pages: none
58+
pull-requests: read
59+
repository-projects: read
60+
security-events: read
61+
statuses: read
62+
63+
runs-on: ${{ matrix.platform == 'linux/amd64' && 'ubuntu-latest' || matrix.platform == 'linux/arm64' && 'ubuntu-24.04-arm' }}
64+
# The job runs on different runners based on the platform.
65+
# For linux/amd64, it runs on the latest Ubuntu runner.
66+
# For linux/arm64, it runs on an Ubuntu 24.04 ARM runner.
67+
# The runner is selected based on the platform specified in the matrix.
68+
69+
name: Build Docker image for ${{ matrix.platform }}
70+
71+
steps:
72+
-
73+
name: Prepare environment for current platform
74+
# This step sets up the environment for the current platform being built.
75+
# It replaces the '/' character in the platform name with '-' and sets it as an environment variable.
76+
# This is useful for naming artifacts and other resources that cannot contain '/'.
77+
# The environment variable PLATFORMS_PAIR will be used later in the workflow.
78+
id: prepare
79+
run: |
80+
platform=${{ matrix.platform }}
81+
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
82+
83+
- name: Checkout
84+
uses: actions/checkout@v5.0.0
85+
# This step checks out the code from the repository.
86+
# It uses the actions/checkout action to clone the repository into the runner's workspace.
87+
88+
- name: Docker meta default
89+
# This step generates metadata for the Docker image.
90+
# It uses the docker/metadata-action to create metadata based on the repository information.
91+
# The metadata includes information such as the image name, tags, and labels.
92+
# The metadata will be used later in the workflow to build and push the Docker image.
93+
id: meta
94+
uses: docker/metadata-action@v5.9.0
95+
with:
96+
images: ${{ env.GHCR_IMAGE }}
97+
98+
- name: Set up Docker Context for Buildx
99+
# This step sets up a Docker context for Buildx.
100+
# It creates a new context named "builders" that will be used for building the Docker image.
101+
# The context allows Buildx to use the Docker daemon for building images.
102+
id: buildx-context
103+
run: |
104+
docker context create builders
105+
106+
- name: Set up Docker Buildx
107+
# This step sets up Docker Buildx, which is a Docker CLI plugin for extended build capabilities with BuildKit.
108+
# It uses the docker/setup-buildx-action to configure Buildx with the specified context and platforms.
109+
# The platforms are specified in the matrix and will be used for building the Docker image.
110+
uses: docker/setup-buildx-action@v3.11.1
111+
with:
112+
endpoint: builders
113+
platforms: ${{ matrix.platform }}
114+
115+
- name: Login to GitHub Container Registry
116+
# This step logs in to the GitHub Container Registry (GHCR) using the docker/login-action.
117+
# It uses the GitHub actor's username and the GITHUB_TOKEN secret for authentication.
118+
uses: docker/login-action@v3.6.0
119+
with:
120+
registry: ghcr.io
121+
username: ${{ github.actor }}
122+
password: ${{ secrets.GITHUB_TOKEN }}
123+
124+
125+
- name: Build and push by digest
126+
# This step builds and pushes the Docker image using Buildx.
127+
# It uses the docker/build-push-action to build the image with the specified context and platforms.
128+
# The image is built with the labels and annotations generated in the previous steps.
129+
# The outputs are configured to push the image by digest, which allows for better caching and versioning.
130+
# The cache-from and cache-to options are used to enable caching for the build process.
131+
# The cache is stored in GitHub Actions cache and is scoped to the repository, branch, and platform.
132+
id: build
133+
uses: docker/build-push-action@v6.18.0
134+
env:
135+
DOCKER_BUILDKIT: 1
136+
with:
137+
context: .
138+
build-args: |
139+
VERSION=${{ steps.meta.outputs.version }}
140+
platforms: ${{ matrix.platform }}
141+
labels: ${{ steps.meta.outputs.labels }}
142+
annotations: ${{ steps.meta.outputs.annotations }}
143+
outputs: type=image,name=${{ env.GHCR_IMAGE }},push-by-digest=true,name-canonical=true,push=true,oci-mediatypes=true
144+
cache-from: type=gha,scope=${{ github.repository }}-${{ github.ref_name }}-${{ matrix.platform }}
145+
cache-to: type=gha,scope=${{ github.repository }}-${{ github.ref_name }}-${{ matrix.platform }}
146+
147+
148+
- name: Export digest
149+
# This step exports the digest of the built image to a file.
150+
# It creates a directory in /tmp/digests and saves the digest of the image to a file.
151+
# The digest is obtained from the output of the build step.
152+
# The digest is used to uniquely identify the built image and can be used for further processing or verification.
153+
run: |
154+
mkdir -p /tmp/digests
155+
digest="${{ steps.build.outputs.digest }}"
156+
touch "/tmp/digests/${digest#sha256:}"
157+
158+
- name: Upload digest
159+
# This step uploads the digest file to the GitHub Actions artifact storage.
160+
# It uses the actions/upload-artifact action to upload the file created in the previous step.
161+
# The artifact is named digests-${{ matrix.platform_pair }}, where platform_pair is the platform name with '/' replaced by '-'.
162+
# The artifact is retained for 1 day, and if no files are found, it will throw an error.
163+
uses: actions/upload-artifact@v5.0.0
164+
with:
165+
name: digests-${{ matrix.platform_pair }}
166+
path: /tmp/digests/*
167+
if-no-files-found: error
168+
retention-days: 1
169+
170+
171+
merge:
172+
# This job merges the Docker manifests for the different platforms built in the previous job.
173+
name: Merge Docker manifests
174+
runs-on: ubuntu-latest
175+
permissions:
176+
attestations: write
177+
actions: read
178+
checks: read
179+
contents: read
180+
deployments: none
181+
id-token: write
182+
issues: read
183+
discussions: read
184+
packages: write
185+
pages: none
186+
pull-requests: read
187+
repository-projects: read
188+
security-events: read
189+
statuses: read
190+
191+
needs:
192+
- build
193+
# This job depends on the build job to complete before it starts.
194+
# It ensures that the Docker images for all platforms are built before merging the manifests.
195+
steps:
196+
- name: Download digests
197+
# This step downloads the digest files uploaded in the build job.
198+
# It uses the actions/download-artifact action to download the artifacts with the pattern digests-*.
199+
# The downloaded files are merged into the /tmp/digests directory.
200+
uses: actions/download-artifact@v6.0.0
201+
with:
202+
path: /tmp/digests
203+
pattern: digests-*
204+
merge-multiple: true
205+
206+
207+
- name: Docker meta
208+
# This step generates metadata for the Docker image.
209+
# It uses the docker/metadata-action to create metadata based on the repository information.
210+
# The metadata includes information such as the image name, tags, and labels.
211+
id: meta
212+
uses: docker/metadata-action@v5.9.0
213+
with:
214+
images: ${{ env.GHCR_IMAGE }}
215+
annotations: |
216+
type=org.opencontainers.image.description,value=${{ github.event.repository.description || 'No description provided' }}
217+
tags: |
218+
type=ref,event=tag
219+
220+
- name: Set up Docker Buildx
221+
uses: docker/setup-buildx-action@v3.11.1
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@v3.6.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 }}'
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Continuous Delivery
1+
name: CD Release and Publish
22

33
on:
44
workflow_run:
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
name: Continuous Integration
1+
name: CI Testing Workflow
22

33
on:
4+
workflow_dispatch:
45
pull_request:
56
branches:
67
- main
78
push:
89
branches:
910
- main
10-
workflow_dispatch:
1111

1212
concurrency:
1313
group: ${{ github.head_ref || github.run_id }}

0 commit comments

Comments
 (0)