mirror of
https://github.com/we-promise/sure.git
synced 2026-04-19 03:54:08 +00:00
feat: introduce multi-arch build matrix and OCI-compliant multi-arch images (#46)
* chore: update Docker image description to 'Sure' * chore: adjust Docker metadata tags in publish workflow - Disable automatic 'latest' tag generation - Add 'nightly' tag for scheduled builds * feat: add optional 'push' input to control image publishing - Allow toggling push behavior via workflow input - Allow push to 'ghcr.io' on tags, scheduled events, or when input is true * chore: switch Docker cache to use registry-based build cache * feat(ci): add multi-arch matrix image build & manifest merge - Build and push images for amd64 and arm64 with platform-specific cache - Export and upload image digests for each platform - Merge digests into multi-arch manifest with retry logic - Remove QEMU setup, use platform-specific runners - Dynamic tag configuration for nightly, stable, and SHA tags * chore(deps): pin external actions to specific minor versions * Bump actions/checkout to v4.2.0 * Bump docker/setup-buildx-action to v3.10.0 * Bump docker/login-action to v3.3.0 * Bump docker/metadata-action to v5.6.0 * Bump docker/build-push-action to v6.16.0 * chore(ci): improve publish workflow robustness - Set artifact upload to error if no files found & limit retention to 1 day * chore(ci): enable OCI media types in Docker build outputs * feat(ci): add scheduled nightly publish at 01:30 UTC * chore(ci): refine image publish workflow and clarify multi-arch steps - Add reference and conditions for push in workflow comments - Rename build step for clarity on published platform target - Ensure oci-mediatypes is preserved for annotations to show - Rename merge job to indicate pushing multi-arch manifest tag
This commit is contained in:
168
.github/workflows/publish.yml
vendored
168
.github/workflows/publish.yml
vendored
@@ -1,3 +1,13 @@
|
||||
# Reference: https://docs.docker.com/build/ci/github-actions/multi-platform/#distribute-build-across-multiple-runners
|
||||
|
||||
# Conditions for pushing the image to GHCR:
|
||||
# - Triggered by push to a version tag (`v*`)
|
||||
# - Triggered by a scheduled run
|
||||
# - Triggered manually via `workflow_dispatch` with `push: true`
|
||||
#
|
||||
# Conditional expression:
|
||||
# startsWith(github.ref, 'refs/tags/v') || github.event_name == 'schedule' || github.event.inputs.push
|
||||
|
||||
name: Publish Docker image
|
||||
|
||||
on:
|
||||
@@ -8,11 +18,18 @@ on:
|
||||
required: true
|
||||
type: string
|
||||
default: 'main'
|
||||
push:
|
||||
description: 'Push the image to container registry'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
branches:
|
||||
- main
|
||||
schedule:
|
||||
- cron: '30 1 * * *'
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
@@ -26,12 +43,19 @@ jobs:
|
||||
uses: ./.github/workflows/ci.yml
|
||||
|
||||
build:
|
||||
name: Build docker image
|
||||
name: Build Docker image
|
||||
needs: [ ci ]
|
||||
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [amd64, arm64]
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
runs-on: ${{ matrix.platform == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
|
||||
|
||||
outputs:
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -39,47 +63,137 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v4.2.0
|
||||
with:
|
||||
ref: ${{ github.event.inputs.ref || github.ref }}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v3.10.0
|
||||
|
||||
- name: Log in to the container registry
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v3.3.0
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Configure image tags
|
||||
id: tag_config
|
||||
shell: bash
|
||||
run: |
|
||||
BASE_CONFIG="type=sha,format=long"
|
||||
if [[ $GITHUB_EVENT_NAME == "schedule" ]]; then
|
||||
BASE_CONFIG+=$'\n'"type=schedule,pattern=nightly"
|
||||
elif [[ "$GITHUB_REF" == refs/tags/v* ]]; then
|
||||
BASE_CONFIG="type=semver,pattern={{version}}"
|
||||
BASE_CONFIG+=$'\n'"type=raw,value=stable"
|
||||
fi
|
||||
{
|
||||
echo 'TAGS_SPEC<<EOF'
|
||||
echo "$BASE_CONFIG"
|
||||
echo EOF
|
||||
} >> $GITHUB_ENV
|
||||
|
||||
- name: Extract metadata for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@v5.6.0
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
flavor: latest=auto
|
||||
tags: |
|
||||
type=sha,format=long
|
||||
type=semver,pattern={{version}}
|
||||
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
|
||||
type=raw,value=stable,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
flavor: latest=false
|
||||
tags: ${{ env.TAGS_SPEC }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v6
|
||||
- name: Publish 'linux/${{ matrix.platform }}' image by digest
|
||||
uses: docker/build-push-action@v6.16.0
|
||||
id: build
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
platforms: 'linux/amd64,linux/arm64'
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
provenance: false
|
||||
# https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry#adding-a-description-to-multi-arch-images
|
||||
outputs: type=image,name=target,annotation-index.org.opencontainers.image.description=A multi-arch Docker image for the Maybe Rails app
|
||||
build-args: BUILD_COMMIT_SHA=${{ github.sha }}
|
||||
platforms: 'linux/${{ matrix.platform }}'
|
||||
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:cache-${{ matrix.platform }}
|
||||
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:cache-${{ matrix.platform }},mode=max
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
provenance: false
|
||||
push: true
|
||||
# DO NOT REMOVE `oci-mediatypes=true`, fixes annotation not showing up on job.merge.steps[-1]
|
||||
# ref: https://github.com/docker/build-push-action/discussions/1022
|
||||
outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},name-canonical=true,push-by-digest=true,oci-mediatypes=true
|
||||
|
||||
- name: Export the Docker image digest
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') || github.event_name == 'schedule' || github.event.inputs.push }}
|
||||
run: |
|
||||
mkdir -p "${RUNNER_TEMP}"/digests
|
||||
echo "${DIGEST#sha256:}" > "${RUNNER_TEMP}/digests/digest-${PLATFORM}"
|
||||
env:
|
||||
DIGEST: ${{ steps.build.outputs.digest }}
|
||||
PLATFORM: ${{ matrix.platform }}
|
||||
|
||||
- name: Upload the Docker image digest
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') || github.event_name == 'schedule' || github.event.inputs.push }}
|
||||
uses: actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: digest-${{ matrix.platform }}
|
||||
path: ${{ runner.temp }}/digests/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
merge:
|
||||
name: Merge multi-arch manifest & push multi-arch tag
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') || github.event_name == 'schedule' || github.event.inputs.push }}
|
||||
needs: [build]
|
||||
|
||||
timeout-minutes: 60
|
||||
runs-on: 'ubuntu-24.04'
|
||||
|
||||
permissions:
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.10.0
|
||||
|
||||
- name: Download Docker image digests
|
||||
uses: actions/download-artifact@v4.3.0
|
||||
with:
|
||||
path: ${{ runner.temp }}/digests
|
||||
pattern: digest-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Log in to the container registry
|
||||
uses: docker/login-action@v3.3.0
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Merge and push Docker image
|
||||
env:
|
||||
TAGS: ${{ needs.build.outputs.tags }}
|
||||
DIGESTS_DIR: ${{ runner.temp }}/digests
|
||||
shell: bash -xeuo pipefail {0}
|
||||
run: |
|
||||
tag_args=()
|
||||
while IFS=$'\n' read -r tag; do
|
||||
[[ -n "${tag}" ]] || continue
|
||||
tag_args+=("--tag=${tag}")
|
||||
done <<< "${TAGS}"
|
||||
|
||||
image_args=()
|
||||
for PLATFORM in amd64 arm64; do
|
||||
image_args+=("${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:$(<"${DIGESTS_DIR}/digest-${PLATFORM}")")
|
||||
done
|
||||
|
||||
attempts=0
|
||||
until docker buildx imagetools create \
|
||||
--annotation "index:org.opencontainers.image.description=A multi-arch Docker image for the Sure Rails app" \
|
||||
"${tag_args[@]}" "${image_args[@]}" \
|
||||
; do
|
||||
attempts=$((attempts + 1))
|
||||
if [[ $attempts -ge 3 ]]; then
|
||||
echo "[$(date -u)] ERROR: Failed after 3 attempts." >&2
|
||||
exit 1
|
||||
fi
|
||||
delay=$((2 ** attempts))
|
||||
if [[ $delay -gt 15 ]]; then delay=15; fi
|
||||
echo "Push failed (attempt $attempts). Retrying in ${delay} seconds..."
|
||||
sleep ${delay}
|
||||
done
|
||||
|
||||
Reference in New Issue
Block a user