# 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 default branch (`main`) # - Triggered by push to a version tag (`v*`) # - Triggered by a scheduled run # - Triggered manually via `workflow_dispatch` with `push: true` # # Conditional expression: # github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') || github.event_name == 'schedule' || github.event.inputs.push name: Publish Docker image on: workflow_dispatch: inputs: ref: description: 'Git ref (tag or commit SHA) to build' 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 IMAGE_NAME: ${{ github.repository }} permissions: contents: read jobs: ci: uses: ./.github/workflows/ci.yml build: name: Build Docker image needs: [ ci ] strategy: fail-fast: false matrix: platform: [amd64, arm64] timeout-minutes: 60 runs-on: ${{ matrix.platform == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }} outputs: tags: ${{ steps.meta.outputs.tags }} permissions: contents: read packages: write steps: - name: Check out the repo uses: actions/checkout@v4.2.0 with: ref: ${{ github.event.inputs.ref || github.ref }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3.10.0 - 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: 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" BASE_CONFIG+=$'\n'"type=schedule,pattern=nightly-{{date 'ddd'}}" elif [[ "$GITHUB_REF" == refs/tags/v* ]]; then BASE_CONFIG="type=semver,pattern={{version}}" BASE_CONFIG+=$'\n'"type=raw,value=stable" fi { echo 'TAGS_SPEC<> $GITHUB_ENV - name: Get current date (RFC 3339 format) id: date run: echo "date=$(date -Iseconds)" >> $GITHUB_OUTPUT - name: Extract metadata for Docker id: meta uses: docker/metadata-action@v5.6.0 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} flavor: latest=false tags: ${{ env.TAGS_SPEC }} labels: | org.opencontainers.image.version=${{ startsWith(github.ref, 'refs/tags/v') && github.ref_name || '' }} org.opencontainers.image.created=${{ steps.date.outputs.date }} org.opencontainers.image.ref.name=${{ github.ref_name }} org.opencontainers.image.vendor=we-promise org.opencontainers.image.title=Sure org.opencontainers.image.description=A multi-arch Docker image for the Sure Rails app - name: Publish 'linux/${{ matrix.platform }}' image by digest uses: docker/build-push-action@v6.16.0 id: build with: context: . 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: ${{ github.ref == 'refs/heads/main' || 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: ${{ github.ref == 'refs/heads/main' || 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: ${{ github.ref == 'refs/heads/main' || 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 annotations=( "index:org.opencontainers.image.created=$(date -Iseconds)" 'index:org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}' 'index:org.opencontainers.image.revision=${{ github.sha }}' 'index:org.opencontainers.image.ref.name=${{ github.ref_name }}' 'index:org.opencontainers.image.vendor=we-promise' 'index:org.opencontainers.image.licenses=AGPL-3.0' 'index:org.opencontainers.image.title=Sure' 'index:org.opencontainers.image.description=A multi-arch Docker image for the Sure Rails app' ) annotation_args=() for annotation in "${annotations[@]}"; do annotation_args+=("--annotation=${annotation}") done if [[ $GITHUB_REF_TYPE == "tag" ]]; then annotation_args+=("--annotation=index:org.opencontainers.image.version=$GITHUB_REF_NAME") fi attempts=0 until docker buildx imagetools create \ "${annotation_args[@]}" \ "${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