ci: publish cac, cac-fpm and shared-httpd multi-arch (amd64 + arm64)

One job per matrix leg on the trusted x86 runner: amd64 built natively, arm64
under QEMU, each pushed BY DIGEST (untagged), then
scripts/assemble-multiarch-index.sh creates the existing tags (:phpNN, :latest
for php85 / shared-httpd :latest) from this run's digests. The arm64 step is
continue-on-error with its own timeout, so an arm64 failure publishes the usual
amd64-only tag and a stale arm64 is never re-attached. Build args, base-image
pin, pkg-cache probe, php.net EXPECT_PHP_MIN gate, secrets and the
cac-litespeed/cac-lsphp/shared-ols jobs are untouched. No Dockerfile change.

Adds scripts/tests/multiarch-workflow.test.py (static wiring + tag-scheme
execution) to Shell-Checks, and fixes index-platforms.test.sh line 17 which
tripped lsphp-info-probe.test.sh's early-exit-reader scan on trunk.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0185ukXjXyosSkunDsdWz3mb
This commit is contained in:
shadowdaoandClaude Sonnet 5.5 committed 2026-11-12 06:13:02 -08:00
1 parent adf74e2f69
commit 01741e735a
3 files changed
+318 -15

No files matched your search

+144 -14
View File
@@ -76,6 +76,11 @@ jobs:
./scripts/tests/image-label-amd64.test.sh
./scripts/tests/index-platforms.test.sh
# Static wiring of the multi-arch publish jobs (cac, cac-fpm, shared-httpd):
# by-digest pushes, non-fatal arm64 leg, timeouts, exact tag scheme.
- name: multi-arch workflow wiring test
run: python3 -I scripts/tests/multiarch-workflow.test.py
- name: lsphp probe regression test
run: ./scripts/tests/lsphp-info-probe.test.sh
@@ -92,6 +97,7 @@ jobs:
Build-and-Push:
runs-on: ubuntu-latest
timeout-minutes: 75
strategy:
# One PHP version failing (e.g. an EOL stream) must not cancel the
# security builds of the others.
@@ -143,6 +149,8 @@ jobs:
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
with:
platforms: arm64
# BuildKit (docker-container driver) ignores dockerd's registry-mirrors,
# so the mirror is configured on the builder; BuildKit itself still falls
@@ -197,23 +205,64 @@ jobs:
username: ${{ secrets.CI_USER }}
password: ${{ secrets.CI_TOKEN }}
- name: Build and Push Image
# amd64: required. Pushed untagged BY DIGEST (no tag); failure fails the
# job and publishes nothing, exactly as before multi-arch. The tags are only
# created by the assemble step below.
- name: Build amd64 (push by digest)
id: amd64
uses: docker/build-push-action@v6
with:
platforms: linux/amd64
push: true
build-args: |
PHPVER=${{ matrix.phpver }}
CACHE_BUST=${{ steps.bust.outputs.value }}
BASE_IMAGE=${{ steps.base.outputs.image }}
PKG_CACHE=${{ steps.pkgcache.outputs.url }}
EXPECT_PHP_MIN=${{ steps.phpmin.outputs.min }}
tags: |
repo.anhonesthost.net/cloud-hosting-platform/cac:php${{ matrix.phpver }}
${{ matrix.phpver == '85' && 'repo.anhonesthost.net/cloud-hosting-platform/cac:latest' || '' }}
outputs: type=image,name=repo.anhonesthost.net/cloud-hosting-platform/cac,push-by-digest=true,name-canonical=true,push=true
# arm64: best effort, built under QEMU on this same trusted x86 runner (no
# secret ever leaves it). Same Dockerfile, same build-args (incl. the php.net
# EXPECT_PHP_MIN gate, which runs `php -r` on the arm64 binary). Must never
# block or alter the amd64 tag: on failure/timeout the tag is published
# amd64-only, as before multi-arch.
- name: Build arm64 (push by digest)
id: arm64
continue-on-error: true
timeout-minutes: 45
uses: docker/build-push-action@v6
with:
platforms: linux/arm64
build-args: |
PHPVER=${{ matrix.phpver }}
CACHE_BUST=${{ steps.bust.outputs.value }}
BASE_IMAGE=${{ steps.base.outputs.image }}
PKG_CACHE=${{ steps.pkgcache.outputs.url }}
EXPECT_PHP_MIN=${{ steps.phpmin.outputs.min }}
outputs: type=image,name=repo.anhonesthost.net/cloud-hosting-platform/cac,push-by-digest=true,name-canonical=true,push=true
# Creates the index tag(s) from THIS run's digests only (a previous run's
# arm64 is never re-attached). scripts/assemble-multiarch-index.sh.
- name: Assemble index tag(s)
env:
AMD64_DIGEST: ${{ steps.amd64.outputs.digest }}
ARM64_DIGEST: ${{ steps.arm64.outputs.digest }}
ARM64_OUTCOME: ${{ steps.arm64.outcome }}
PHPVER: ${{ matrix.phpver }}
run: |
set -euo pipefail
tags=(--tag "php${PHPVER}")
# :latest tracks php85 (unchanged tag scheme).
[ "$PHPVER" = 85 ] && tags+=(--tag latest)
scripts/assemble-multiarch-index.sh \
--amd64 "$AMD64_DIGEST" \
--arm64 "$ARM64_DIGEST" --arm64-outcome "$ARM64_OUTCOME" \
--repo repo.anhonesthost.net/cloud-hosting-platform/cac \
"${tags[@]}"
Build-FPM-Images:
runs-on: ubuntu-latest
timeout-minutes: 75
strategy:
# One PHP version failing (e.g. an EOL stream) must not cancel the
# security builds of the others.
@@ -265,6 +314,8 @@ jobs:
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
with:
platforms: arm64
# BuildKit (docker-container driver) ignores dockerd's registry-mirrors,
# so the mirror is configured on the builder; BuildKit itself still falls
@@ -319,21 +370,62 @@ jobs:
username: ${{ secrets.CI_USER }}
password: ${{ secrets.CI_TOKEN }}
- name: Build and Push FPM Image
# amd64: required. Pushed untagged BY DIGEST (no tag); failure fails the
# job and publishes nothing, exactly as before multi-arch. The tags are only
# created by the assemble step below.
- name: Build amd64 (push by digest)
id: amd64
uses: docker/build-push-action@v6
with:
file: ./Dockerfile.fpm
platforms: linux/amd64
push: true
build-args: |
PHPVER=${{ matrix.phpver }}
CACHE_BUST=${{ steps.bust.outputs.value }}
BASE_IMAGE=${{ steps.base.outputs.image }}
PKG_CACHE=${{ steps.pkgcache.outputs.url }}
EXPECT_PHP_MIN=${{ steps.phpmin.outputs.min }}
tags: |
repo.anhonesthost.net/cloud-hosting-platform/cac-fpm:php${{ matrix.phpver }}
${{ matrix.phpver == '85' && 'repo.anhonesthost.net/cloud-hosting-platform/cac-fpm:latest' || '' }}
outputs: type=image,name=repo.anhonesthost.net/cloud-hosting-platform/cac-fpm,push-by-digest=true,name-canonical=true,push=true
# arm64: best effort, built under QEMU on this same trusted x86 runner (no
# secret ever leaves it). Same Dockerfile, same build-args (incl. the php.net
# EXPECT_PHP_MIN gate, which runs `php -r` on the arm64 binary). Must never
# block or alter the amd64 tag: on failure/timeout the tag is published
# amd64-only, as before multi-arch.
- name: Build arm64 (push by digest)
id: arm64
continue-on-error: true
timeout-minutes: 45
uses: docker/build-push-action@v6
with:
file: ./Dockerfile.fpm
platforms: linux/arm64
build-args: |
PHPVER=${{ matrix.phpver }}
CACHE_BUST=${{ steps.bust.outputs.value }}
BASE_IMAGE=${{ steps.base.outputs.image }}
PKG_CACHE=${{ steps.pkgcache.outputs.url }}
EXPECT_PHP_MIN=${{ steps.phpmin.outputs.min }}
outputs: type=image,name=repo.anhonesthost.net/cloud-hosting-platform/cac-fpm,push-by-digest=true,name-canonical=true,push=true
# Creates the index tag(s) from THIS run's digests only (a previous run's
# arm64 is never re-attached). scripts/assemble-multiarch-index.sh.
- name: Assemble index tag(s)
env:
AMD64_DIGEST: ${{ steps.amd64.outputs.digest }}
ARM64_DIGEST: ${{ steps.arm64.outputs.digest }}
ARM64_OUTCOME: ${{ steps.arm64.outcome }}
PHPVER: ${{ matrix.phpver }}
run: |
set -euo pipefail
tags=(--tag "php${PHPVER}")
# :latest tracks php85 (unchanged tag scheme).
[ "$PHPVER" = 85 ] && tags+=(--tag latest)
scripts/assemble-multiarch-index.sh \
--amd64 "$AMD64_DIGEST" \
--arm64 "$ARM64_DIGEST" --arm64-outcome "$ARM64_OUTCOME" \
--repo repo.anhonesthost.net/cloud-hosting-platform/cac-fpm \
"${tags[@]}"
Build-LiteSpeed-Images:
runs-on: ubuntu-latest
@@ -647,6 +739,7 @@ jobs:
Build-Shared-httpd:
runs-on: ubuntu-latest
timeout-minutes: 50
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -692,6 +785,8 @@ jobs:
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
with:
platforms: arm64
# BuildKit (docker-container driver) ignores dockerd's registry-mirrors,
# so the mirror is configured on the builder; BuildKit itself still falls
@@ -717,17 +812,52 @@ jobs:
username: ${{ secrets.CI_USER }}
password: ${{ secrets.CI_TOKEN }}
- name: Build and Push Shared httpd Image
# amd64: required. Pushed untagged BY DIGEST (no tag); failure fails the
# job and publishes nothing, exactly as before multi-arch. The tags are only
# created by the assemble step below.
- name: Build amd64 (push by digest)
id: amd64
uses: docker/build-push-action@v6
with:
file: ./Dockerfile.shared-httpd
platforms: linux/amd64
push: true
build-args: |
BASE_IMAGE=${{ steps.base.outputs.image }}
PKG_CACHE=${{ steps.pkgcache.outputs.url }}
tags: |
repo.anhonesthost.net/cloud-hosting-platform/shared-httpd:latest
outputs: type=image,name=repo.anhonesthost.net/cloud-hosting-platform/shared-httpd,push-by-digest=true,name-canonical=true,push=true
# arm64: best effort, built under QEMU on this same trusted x86 runner (no
# secret ever leaves it). Same Dockerfile, same build-args (incl. the php.net
# EXPECT_PHP_MIN gate, which runs `php -r` on the arm64 binary). Must never
# block or alter the amd64 tag: on failure/timeout the tag is published
# amd64-only, as before multi-arch.
- name: Build arm64 (push by digest)
id: arm64
continue-on-error: true
timeout-minutes: 25
uses: docker/build-push-action@v6
with:
file: ./Dockerfile.shared-httpd
platforms: linux/arm64
build-args: |
BASE_IMAGE=${{ steps.base.outputs.image }}
PKG_CACHE=${{ steps.pkgcache.outputs.url }}
outputs: type=image,name=repo.anhonesthost.net/cloud-hosting-platform/shared-httpd,push-by-digest=true,name-canonical=true,push=true
# Creates the index tag(s) from THIS run's digests only (a previous run's
# arm64 is never re-attached). scripts/assemble-multiarch-index.sh.
- name: Assemble index tag(s)
env:
AMD64_DIGEST: ${{ steps.amd64.outputs.digest }}
ARM64_DIGEST: ${{ steps.arm64.outputs.digest }}
ARM64_OUTCOME: ${{ steps.arm64.outcome }}
run: |
set -euo pipefail
scripts/assemble-multiarch-index.sh \
--amd64 "$AMD64_DIGEST" \
--arm64 "$ARM64_DIGEST" --arm64-outcome "$ARM64_OUTCOME" \
--repo repo.anhonesthost.net/cloud-hosting-platform/shared-httpd \
--tag latest
Build-Shared-OLS:
runs-on: ubuntu-latest