Files
temetro/.github/workflows/release.yml
T
Khalid Abdi 07cfd5d335 ci: build release images on native arm64 runners, not QEMU
The release build emulated linux/arm64 with QEMU on the amd64 runner. A
runner/QEMU update started crashing 'npm ci' under emulation (illegal
instruction) and the build then hung for hours instead of failing, so
v0.17.0 never published. Build each arch on its native runner instead
(amd64 on ubuntu-24.04, arm64 on the free ubuntu-24.04-arm) and merge the
per-arch digests into one manifest with buildx imagetools. Add per-job
timeouts so a stuck build can never run for hours again.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 02:12:27 +03:00

170 lines
5.8 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Builds and publishes the temetro Docker images and cuts a GitHub Release.
#
# Trigger: push a semver tag, e.g.
# git tag v0.1.0 && git push origin v0.1.0
#
# Multi-arch (amd64 + arm64) is built on NATIVE runners — amd64 on ubuntu-24.04,
# arm64 on ubuntu-24.04-arm — and merged into a manifest. We do NOT emulate arm64
# with QEMU anymore: a runner/QEMU update started crashing `npm ci` under
# emulation ("illegal instruction") and hanging the build for hours.
#
# The frontend image bakes NO API URL — it resolves the backend from the host
# the browser uses at runtime — so one published image works for every clinic.
#
# Required repository secrets (Settings → Secrets and variables → Actions):
# DOCKERHUB_USERNAME — the Docker Hub account `khalidxv` (or one with push
# access to that namespace)
# DOCKERHUB_TOKEN — a Docker Hub access token for that account
# Without them the build/login step fails; the workflow is otherwise inert.
name: release
on:
push:
tags:
- "v*.*.*"
env:
REGISTRY_NAMESPACE: khalidxv
jobs:
# One build per (image × platform) on the platform's native runner, pushed to
# Docker Hub by digest (no tag yet). The merge job stitches the per-arch
# digests into a single tagged multi-arch manifest.
build:
name: Build ${{ matrix.image }} (${{ matrix.platform }})
runs-on: ${{ matrix.platform == 'linux/arm64' && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
timeout-minutes: 40
strategy:
fail-fast: false
matrix:
image: [backend, frontend]
platform: [linux/amd64, linux/arm64]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Prepare platform pair
run: |
platform="${{ matrix.platform }}"
echo "PLATFORM_PAIR=${platform//\//-}" >> "$GITHUB_ENV"
- name: Set up Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build & push by digest
id: build
uses: docker/build-push-action@v6
with:
context: ./${{ matrix.image }}
platforms: ${{ matrix.platform }}
provenance: false
outputs: type=image,name=${{ env.REGISTRY_NAMESPACE }}/temetro-${{ matrix.image }},push-by-digest=true,name-canonical=true,push=true
- name: Export digest
run: |
mkdir -p "${{ runner.temp }}/digests"
digest="${{ steps.build.outputs.digest }}"
touch "${{ runner.temp }}/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digests-${{ matrix.image }}-${{ env.PLATFORM_PAIR }}
path: ${{ runner.temp }}/digests/*
if-no-files-found: error
retention-days: 1
# Combine the per-arch digests for each image into one multi-arch manifest and
# tag it (X.Y.Z + latest).
merge:
name: Merge ${{ matrix.image }} manifest
runs-on: ubuntu-latest
timeout-minutes: 15
needs: build
strategy:
fail-fast: false
matrix:
image: [backend, frontend]
steps:
- name: Derive version from tag
id: meta
run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
- name: Download digests
uses: actions/download-artifact@v4
with:
path: ${{ runner.temp }}/digests
pattern: digests-${{ matrix.image }}-*
merge-multiple: true
- name: Set up Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Create manifest list and push
working-directory: ${{ runner.temp }}/digests
env:
IMAGE: ${{ env.REGISTRY_NAMESPACE }}/temetro-${{ matrix.image }}
VERSION: ${{ steps.meta.outputs.version }}
run: |
docker buildx imagetools create \
-t "$IMAGE:$VERSION" \
-t "$IMAGE:latest" \
$(printf "$IMAGE@sha256:%s " *)
- name: Inspect
env:
IMAGE: ${{ env.REGISTRY_NAMESPACE }}/temetro-${{ matrix.image }}
VERSION: ${{ steps.meta.outputs.version }}
run: docker buildx imagetools inspect "$IMAGE:$VERSION"
# Both images are published — now cut the GitHub Release with the changelog.
release:
name: GitHub Release
runs-on: ubuntu-latest
timeout-minutes: 10
needs: merge
permissions:
contents: write # create the GitHub Release (the update check reads this)
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Derive version from tag
id: meta
run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
# Pull this version's section out of CHANGELOG.md so the release has real,
# human-written notes (the auto "Full Changelog" link is still appended
# below via generate_release_notes). Falls back to a generic line if the
# version has no CHANGELOG entry.
- name: Extract changelog notes
id: notes
run: |
version="${{ steps.meta.outputs.version }}"
awk -v v="$version" '
$0 ~ "^## \\[" v "\\]" {flag=1; next}
/^## \[/ {flag=0}
flag {print}
' CHANGELOG.md | sed '/./,$!d' > release-notes.md
if [ ! -s release-notes.md ]; then
echo "Release $version. See the changelog for details." > release-notes.md
fi
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
body_path: release-notes.md
generate_release_notes: true