Files
sure/.github/workflows/publish.yml
Himank Dave eba42e2f71 build: allow main branch publishing and add comprehensive OCI metadata for multi-arch images (#65)
* feat(ci): add OCI annotations and metadata to multi-arch images

- Include created, source, revision, ref-name, vendor & license
- Add title and description for Sure Rails app multi-arch image
- Dynamically annotate version when tag builds are triggered

* chore(ci): add nightly tag with weekday pattern to tag config

* fix(ci): allow publish workflow to push images from main branch

- Update conditional checks to include refs/heads/main
- Reflect new condition in workflow comments for clarity

* fix(ci): set image version label only for tag builds in metadata

* fix(ci): avoid quotations being passed as CLI argument

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Himank Dave <93311724+steadyfall@users.noreply.github.com>

* refactor(ci): remove deprecated `::set-output` command

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Himank Dave <93311724+steadyfall@users.noreply.github.com>

---------

Signed-off-by: Himank Dave <93311724+steadyfall@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-05 00:41:05 +02:00

232 lines
8.1 KiB
YAML

# 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<<EOF'
echo "$BASE_CONFIG"
echo EOF
} >> $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