ci: build the image once instead of twice #5

Merged
lyrathorpe merged 1 commits from ci/one-build-not-two into main 2026-08-24 17:32:02 +01:00
Owner

Measured

Recent runs on this repo: 15.3 min (pull request), 18.3 min (push). The
image is built twice, in full, and the second build is pure waste.

Phase Time
docker build --target test (daemon) ~5 min
setup-buildx-action booting buildkit 75.8s
build-push-action --target runtime (buildx) ~7 min

The second build re-pulls the base, then runs apk add --no-cache ffmpeg
(~110s) and pip install (~101s) again, producing nothing the first build had
not already made. setup-buildx-action creates a builder in its own container
with its own cache, so it shares nothing with the daemon that just built the
test stage -- despite the comment above that step claiming the layers were
shared. That comment is why this looked fine.

buildx earns its overhead when producing several architectures. This workflow
produces linux/amd64 only, by an explicit decision recorded in it.

Change

Plain docker build against the same daemon that ran the tests, then
docker push. --target runtime is a strict prefix of the test stage, so
every layer is a cache hit.

Measured locally on this repo: 1.3 seconds, against roughly eight minutes
in CI for buildkit boot plus a full rebuild including ffmpeg.

Identical to lyrathorpe/music-curator#7, whose workflow this one was copied
from -- the defect travelled with it.

What this does not fix

The runner is slow in absolute terms: pip install pytest takes 28 seconds,
layer export 31 seconds. There is also no cross-run cache, so the base image
and ffmpeg are fetched fresh every job. Fixing that needs a registry-backed
cache and is worth doing only if the remaining time still hurts.

## Measured Recent runs on this repo: **15.3 min** (pull request), **18.3 min** (push). The image is built twice, in full, and the second build is pure waste. | Phase | Time | | ------------------------------------------- | ------- | | `docker build --target test` (daemon) | ~5 min | | `setup-buildx-action` booting buildkit | 75.8s | | `build-push-action --target runtime` (buildx) | ~7 min | The second build re-pulls the base, then runs `apk add --no-cache ffmpeg` (~110s) and `pip install` (~101s) again, producing nothing the first build had not already made. `setup-buildx-action` creates a builder in its own container with its own cache, so it shares nothing with the daemon that just built the test stage -- despite the comment above that step claiming the layers were shared. That comment is why this looked fine. buildx earns its overhead when producing several architectures. This workflow produces `linux/amd64` only, by an explicit decision recorded in it. ## Change Plain `docker build` against the same daemon that ran the tests, then `docker push`. `--target runtime` is a strict prefix of the test stage, so every layer is a cache hit. Measured locally on this repo: **1.3 seconds**, against roughly eight minutes in CI for buildkit boot plus a full rebuild including ffmpeg. Identical to lyrathorpe/music-curator#7, whose workflow this one was copied from -- the defect travelled with it. ## What this does not fix The runner is slow in absolute terms: `pip install pytest` takes 28 seconds, layer export 31 seconds. There is also no cross-run cache, so the base image and ffmpeg are fetched fresh every job. Fixing that needs a registry-backed cache and is worth doing only if the remaining time still hurts.
lyrathorpe added 1 commit 2026-08-24 17:30:15 +01:00
ci: build the image once instead of twice
Build and publish container / build (pull_request) Successful in 4m4s
ef59e52bca
Runs take fifteen to eighteen minutes, and the log shows why: the image is
built twice, in full.

The test stage is built by the runner's docker daemon. The runtime stage was
then built by docker/build-push-action, which runs under a buildx builder that
setup-buildx-action creates in its own container with its own cache. The two
share nothing, so the second build spent seventy-six seconds booting buildkit
and then installed ffmpeg and the package all over again -- around a hundred
and ten seconds for the apk and another hundred for pip, neither of which
produced anything the first build had not already made. The comment above the
test step claimed those layers were shared, which is what made this look
reasonable.

buildx earns that overhead when producing several architectures. This produces
linux/amd64 only, by an explicit decision recorded in the workflow, so it earns
nothing here. Use plain docker build against the same daemon that ran the
tests, and push with docker push. The runtime stage is a strict prefix of the
test stage, so every layer is a cache hit: measured at 1.3 seconds locally.

Identical to the change made in music-curator, whose workflow this one was
copied from.
lyrathorpe merged commit d5f4329f46 into main 2026-08-24 17:32:02 +01:00
lyrathorpe deleted branch ci/one-build-not-two 2026-08-24 17:32:03 +01:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: lyrathorpe/music-mirror#5