From de11d347abc68fcbed84dd0159e92f2279c21ee3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Georger?= Date: Wed, 21 Oct 2026 10:36:13 -0300 Subject: [PATCH] Add multi-arch Docker image build workflow with GitHub Actions --- .github/workflows/multi-build-gateway.yaml | 276 +++++++++++++++++++++ 1 file changed, 276 insertions(+) create mode 100644 .github/workflows/multi-build-gateway.yaml diff --git a/.github/workflows/multi-build-gateway.yaml b/.github/workflows/multi-build-gateway.yaml new file mode 100644 index 0000000..545df3c --- /dev/null +++ b/.github/workflows/multi-build-gateway.yaml @@ -0,0 +1,276 @@ +# 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. +# It uses Docker Buildx to build and push the image to GitHub Container Registry (GHCR). +name: Gateway Build multi arch Docker Image with separate Github Runners + +on: + workflow_dispatch: + push: + branches: + - main + paths: + - 'examples/dsh-agents-as-a-service/gateway/**' + - '.github/workflows/multi-build-gateway.yaml' +env: + # The name of the Docker image to be built and pushed to GHCR + # The image name is derived from the GitHub repository name and the GitHub Container Registry (GHCR) URL. + # The image name will be in the format: ghcr.io// + GHCR_IMAGE: ghcr.io/${{ github.repository }} +permissions: + # Global permissions for the workflow, which can be overridden at the job level + contents: read + +concurrency: + # This concurrency group ensures that only one job in the group runs at a time. + # If a new job is triggered, the previous one will be canceled. + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + # The build job builds the Docker image for each platform specified in the matrix. + build: + strategy: + fail-fast: false + matrix: + platform: + - linux/amd64 + - linux/arm64 + # The matrix includes two platforms: linux/amd64 and linux/arm64. + # The build job will run for each platform in the matrix. + + permissions: + # Permissions for the build job, which can be overridden at the step level + # The permissions are set to allow the job to write to the GitHub Container Registry (GHCR) and read from the repository. + attestations: write + actions: read + checks: write + contents: write + deployments: none + id-token: write + issues: read + discussions: read + packages: write + pages: none + pull-requests: read + repository-projects: read + security-events: read + statuses: read + + runs-on: ${{ matrix.platform == 'linux/amd64' && 'ubuntu-latest' || matrix.platform == 'linux/arm64' && 'ubuntu-24.04-arm' }} + # The job runs on different runners based on the platform. + # For linux/amd64, it runs on the latest Ubuntu runner. + # For linux/arm64, it runs on an Ubuntu 24.04 ARM runner. + # The runner is selected based on the platform specified in the matrix. + + name: Build Docker image for ${{ matrix.platform }} + + steps: + - + name: Prepare environment for current platform + # This step sets up the environment for the current platform being built. + # It replaces the '/' character in the platform name with '-' and sets it as an environment variable. + # This is useful for naming artifacts and other resources that cannot contain '/'. + # The environment variable PLATFORMS_PAIR will be used later in the workflow. + id: prepare + run: | + platform=${{ matrix.platform }} + echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV + + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + # This step checks out the code from the repository. + # It uses the actions/checkout action to clone the repository into the runner's workspace. + + - name: Docker meta default + # This step generates metadata for the Docker image. + # It uses the docker/metadata-action to create metadata based on the repository information. + # The metadata includes information such as the image name, tags, and labels. + # The metadata will be used later in the workflow to build and push the Docker image. + id: meta + uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 + with: + images: ${{ env.GHCR_IMAGE }} + tags: | + type=raw,value=gateway + + + - name: Set up Docker Context for Buildx + # This step sets up a Docker context for Buildx. + # It creates a new context named "builders" that will be used for building the Docker image. + # The context allows Buildx to use the Docker daemon for building images. + id: buildx-context + run: | + docker context create builders + + - name: Set up Docker Buildx + # This step sets up Docker Buildx, which is a Docker CLI plugin for extended build capabilities with BuildKit. + # It uses the docker/setup-buildx-action to configure Buildx with the specified context and platforms. + # The platforms are specified in the matrix and will be used for building the Docker image. + uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 + with: + endpoint: builders + platforms: ${{ matrix.platform }} + + - name: Login to GitHub Container Registry + # This step logs in to the GitHub Container Registry (GHCR) using the docker/login-action. + # It uses the GitHub actor's username and the GITHUB_TOKEN secret for authentication. + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + + - name: Build and push by digest + # This step builds and pushes the Docker image using Buildx. + # It uses the docker/build-push-action to build the image with the specified context and platforms. + # The image is built with the labels and annotations generated in the previous steps. + # The outputs are configured to push the image by digest, which allows for better caching and versioning. + # The cache-from and cache-to options are used to enable caching for the build process. + # The cache is stored in GitHub Actions cache and is scoped to the repository, branch, and platform. + id: build + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 + env: + DOCKER_BUILDKIT: 1 + with: + context: ./examples/dsh-agents-as-a-service/gateway/ + platforms: ${{ matrix.platform }} + labels: ${{ steps.meta.outputs.labels }} + annotations: ${{ steps.meta.outputs.annotations }} + outputs: type=image,name=${{ env.GHCR_IMAGE }},push-by-digest=true,name-canonical=true,push=true,oci-mediatypes=true + cache-from: type=gha,scope=${{ github.repository }}-${{ github.ref_name }}-${{ matrix.platform }} + cache-to: type=gha,scope=${{ github.repository }}-${{ github.ref_name }}-${{ matrix.platform }} + + + - name: Export digest + # This step exports the digest of the built image to a file. + # It creates a directory in /tmp/digests and saves the digest of the image to a file. + # The digest is obtained from the output of the build step. + # The digest is used to uniquely identify the built image and can be used for further processing or verification. + run: | + mkdir -p /tmp/digests + digest="${{ steps.build.outputs.digest }}" + touch "/tmp/digests/${digest#sha256:}" + + - name: Upload digest + # This step uploads the digest file to the GitHub Actions artifact storage. + # It uses the actions/upload-artifact action to upload the file created in the previous step. + # The artifact is named digests-${{ env.PLATFORM_PAIR }}, where PLATFORM_PAIR is the platform name with '/' replaced by '-'. + # The artifact is retained for 1 day, and if no files are found, it will throw an error. + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: digests-${{ env.PLATFORM_PAIR }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + + merge: + # This job merges the Docker manifests for the different platforms built in the previous job. + name: Merge Docker manifests + runs-on: ubuntu-latest + permissions: + attestations: write + actions: read + checks: read + contents: read + deployments: none + id-token: write + issues: read + discussions: read + packages: write + pages: none + pull-requests: read + repository-projects: read + security-events: read + statuses: read + + needs: + - build + # This job depends on the build job to complete before it starts. + # It ensures that the Docker images for all platforms are built before merging the manifests. + steps: + - name: Download digests + # This step downloads the digest files uploaded in the build job. + # It uses the actions/download-artifact action to download the artifacts with the pattern digests-*. + # The downloaded files are merged into the /tmp/digests directory. + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 + with: + path: /tmp/digests + pattern: digests-* + merge-multiple: true + + + - name: Docker meta + # This step generates metadata for the Docker image. + # It uses the docker/metadata-action to create metadata based on the repository information. + # The metadata includes information such as the image name, tags, and labels. + id: meta + uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 + with: + images: ${{ env.GHCR_IMAGE }} + annotations: | + type=org.opencontainers.image.description,value=${{ github.event.repository.description || 'No description provided' }} + tags: | + type=raw,value=gateway + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 + # This step sets up Docker Buildx, which is a Docker CLI plugin for extended build capabilities with BuildKit. + with: + driver-opts: | + network=host + + - name: Login to GitHub Container Registry + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 + # This step logs in to the GitHub Container Registry (GHCR) using the docker/login-action. + # It uses the GitHub actor's username and the GITHUB_TOKEN secret for authentication. + # The login is necessary to push the merged manifest list to GHCR. + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Get execution timestamp with RFC3339 format + # This step gets the current execution timestamp in RFC3339 format. + # It uses the date command to get the current UTC time and formats it as a string. + # The timestamp is used for annotating the Docker manifest list. + id: timestamp + run: | + echo "timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")" >> $GITHUB_OUTPUT + + - name: Create manifest list and pushs + # This step creates a manifest list for the Docker images built for different platforms. + # It uses the docker buildx imagetools create command to create the manifest list. + # The manifest list is annotated with metadata such as description, creation timestamp, and source URL. + # The annotations are obtained from the metadata generated in the previous steps. + # The manifest list is pushed to the GitHub Container Registry (GHCR) with the specified tags. + working-directory: /tmp/digests + id: manifest-annotate + continue-on-error: true + run: | + docker buildx imagetools create \ + $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + --annotation='index:org.opencontainers.image.description=${{ github.event.repository.description }}' \ + --annotation='index:org.opencontainers.image.created=${{ steps.timestamp.outputs.timestamp }}' \ + --annotation='index:org.opencontainers.image.url=${{ github.event.repository.url }}' \ + --annotation='index:org.opencontainers.image.source=${{ github.event.repository.url }}' \ + $(printf '${{ env.GHCR_IMAGE }}@sha256:%s ' *) + + - name: Create manifest list and push without annotations + # This step creates a manifest list for the Docker images built for different platforms. + # It uses the docker buildx imagetools create command to create the manifest list. + # The manifest list is created without annotations if the previous step fails. + # The manifest list is pushed to the GitHub Container Registry (GHCR) with the specified tags. + if: steps.manifest-annotate.outcome == 'failure' + working-directory: /tmp/digests + run: | + docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ env.GHCR_IMAGE }}@sha256:%s ' *) + + - name: Inspect image + # This step inspects the created manifest list to verify its contents. + # It uses the docker buildx imagetools inspect command to display information about the manifest list. + # The inspection output will show the platforms and tags associated with the manifest list. + id: inspect + run: | + docker buildx imagetools inspect '${{ env.GHCR_IMAGE }}:${{ steps.meta.outputs.version }}'