ci(webview): adopt registry-cache backend, mirror docker-build.yaml

Both Docker-build steps in build-webview.yaml had ad-hoc caching that
left the bulk of layer state on the floor:

* `build-docker-image` (Pi 1-4 / Qt 5 builder) used
  `--cache-from screenly/ose-qt-builder:latest`, which is the
  image-tag-as-cache trick — only reuses the final manifest, never the
  apt-install + Qt cross-build intermediate layers, and silently no-ops
  the first time after a Dockerfile reorder invalidates the tag.
* `compile-webview-part-2` (Qt 6 / pi5+pi4-64+x86) shipped with
  `docker compose build` and zero cache config, so every PR rebuilt the
  per-board Qt 6 builder image cold.

Switch both to BuildKit's registry cache backend, identical pattern to
docker-build.yaml's `buildx` job: cache pushed to
`ghcr.io/screenly/anthias-webview-qt5-builder:buildcache` (Qt 5) and
`ghcr.io/screenly/anthias-webview-qt6-builder:buildcache-<board>`
(Qt 6, scoped per-board because the three Dockerfiles share almost
nothing). `mode=max,image-manifest=true` because GHCR rejects the
legacy standalone-cache manifest format on `ghcr.io/screenly/*`, same
constraint that bit the main workflow.

Auth-side details:

* Both jobs gain `permissions: { contents: read, packages: write }`,
  scoped per-job so other jobs don't inherit GHCR push.
* New "Login to GitHub Container Registry" step on each, gated on
  `event_name != 'pull_request'`. Fork PRs hand out a read-only
  GITHUB_TOKEN — cache-to would 401 mid-build — so `cache-to` is
  pushed-only-on-push, while `cache-from` runs unconditionally and
  warm-starts PRs off the latest master cache once the buildcache
  package is flipped public (same convention as anthias-server etc.).

Qt 6 build step had to switch from `docker compose build` to
`docker buildx bake -f docker-compose.yml --load --set <target>.cache-*`
because compose's YAML can't carry env-var-conditional cache_to without
emitting an empty list entry that buildx rejects. To keep the
subsequent `docker compose run` happy, the three Qt 6 services in
webview/docker-compose.yml gain explicit `image:` tags
(`webview-builder-{x86,pi5,pi4-64}`) so bake's `--load` puts the image
under a name compose looks up by tag rather than rebuilding it.

The Qt 5 job's old `Set buildx arguments` step (which assembled a
quoted string in $GITHUB_OUTPUT) is gone — build args inline in the
final `docker buildx build` invocation now, no GITHUB_OUTPUT
round-trip.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Viktor Petersson
2026-04-30 05:40:43 +00:00
parent 5e2891987d
commit eadd83d1ac
2 changed files with 70 additions and 16 deletions

View File

@@ -22,6 +22,11 @@ jobs:
build-docker-image:
name: Build Docker Images (Pi 1-4)
runs-on: ubuntu-24.04
# `packages: write` so `docker login ghcr.io` with GITHUB_TOKEN can push
# the buildcache tag below. Mirrors docker-build.yaml's `buildx` job.
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
@@ -40,27 +45,35 @@ jobs:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Set buildx arguments
if: github.event_name != 'pull_request'
id: prepare
run: |
GIT_SHORT_HASH=$(git rev-parse --short HEAD)
{
echo "buildx_args=--cache-from \"screenly/ose-qt-builder:latest\" \
--output \"type=image,push=true\" \
--build-arg \"BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')\" \
--build-arg \"GIT_HASH=$GIT_SHORT_HASH\" \
--build-arg \"GIT_SHORT_HASH=$GIT_SHORT_HASH\" \
--build-arg \"GIT_BRANCH=$GITHUB_REF_NAME\""
} >> "$GITHUB_OUTPUT"
- name: Login to GitHub Container Registry
if: success() && github.event_name != 'pull_request'
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Building container
if: github.event_name != 'pull_request'
env:
# Mirrors docker-build.yaml's registry-cache pattern. Cache lives
# at ghcr.io/screenly/anthias-webview-qt5-builder:buildcache and
# is reused across runs of this job. mode=max writes every layer
# (not just the final manifest) so apt + Qt cross-build state
# survives. image-manifest=true is what GHCR requires; legacy
# standalone-cache manifests are rejected on ghcr.io/screenly/*.
CACHE_REF: ghcr.io/screenly/anthias-webview-qt5-builder:buildcache
run: |
cd webview
GIT_SHORT_HASH=$(git rev-parse --short HEAD)
docker buildx build \
${{ steps.prepare.outputs.buildx_args }} \
--cache-from "type=registry,ref=${CACHE_REF}" \
--cache-to "type=registry,ref=${CACHE_REF},mode=max,image-manifest=true" \
--output "type=image,push=true" \
--build-arg "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \
--build-arg "GIT_HASH=${GIT_SHORT_HASH}" \
--build-arg "GIT_SHORT_HASH=${GIT_SHORT_HASH}" \
--build-arg "GIT_BRANCH=${GITHUB_REF_NAME}" \
-t screenly/ose-qt-builder:latest .
compile-webview-part-1:
@@ -145,6 +158,14 @@ jobs:
strategy:
matrix:
board: ['pi5', 'pi4-64', 'x86']
# `packages: write` so the build step below can push the buildcache
# tag to GHCR on push events. PRs from forks won't have this scope
# in practice (GITHUB_TOKEN is read-only on fork PRs), which is why
# `cache-to` is gated to non-PR events; cache-from still works
# because the published cache package is public.
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
@@ -155,6 +176,14 @@ jobs:
id: buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
- name: Login to GitHub Container Registry
if: success() && github.event_name != 'pull_request'
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set environment variables
run: |
# WebView-v2026.04.0 → 2026.04.0 on tag pushes; date-stamped
@@ -172,9 +201,31 @@ jobs:
} >> "$GITHUB_ENV"
- name: Build Docker Image
env:
# Mirrors docker-build.yaml's registry-cache pattern, scoped per
# board because each Qt 6 board uses a distinct Dockerfile
# (Dockerfile.{pi4,pi5,x86}) and shares almost nothing across
# boards. cache-to is push-only: PRs from forks have a read-only
# GITHUB_TOKEN and would auth-fail on the registry write.
CACHE_REF: ghcr.io/screenly/anthias-webview-qt6-builder:buildcache-${{ matrix.board }}
BAKE_TARGET: builder-${{ matrix.board }}
run: |
cd webview
docker compose build
# `docker buildx bake` reads the compose file, then --set lets us
# inject cache-from / cache-to per target without modifying the
# YAML (which has to stay PR-fork-safe). --load makes the
# resulting image available to the local Docker daemon so the
# subsequent `docker compose run` step finds it via the
# explicit `image:` tag in docker-compose.yml.
set_args=( --set "${BAKE_TARGET}.cache-from=type=registry,ref=${CACHE_REF}" )
if [[ "${{ github.event_name }}" != "pull_request" ]]; then
set_args+=( --set "${BAKE_TARGET}.cache-to=type=registry,ref=${CACHE_REF},mode=max,image-manifest=true" )
fi
docker buildx bake \
-f docker-compose.yml \
--load \
"${set_args[@]}" \
"${BAKE_TARGET}"
- name: Prepare bind-mount target for non-root builder user
run: |

View File

@@ -2,6 +2,7 @@ services:
builder-x86:
profiles:
- x86
image: webview-builder-x86
build:
context: .
dockerfile: docker/Dockerfile.x86
@@ -17,6 +18,7 @@ services:
builder-pi5:
profiles:
- pi5
image: webview-builder-pi5
build:
context: .
dockerfile: docker/Dockerfile.pi5
@@ -33,6 +35,7 @@ services:
builder-pi4-64:
profiles:
- pi4-64
image: webview-builder-pi4-64
build:
context: .
dockerfile: docker/Dockerfile.pi4