Skip to content

Build the docs CI image once and let the registry share it - #70618

Merged
potiuk merged 3 commits into
mainfrom
share-ci-image-docs-registry
Jul 28, 2026
Merged

Build the docs CI image once and let the registry share it#70618
potiuk merged 3 commits into
mainfrom
share-ci-image-docs-registry

Conversation

@potiuk

@potiuk potiuk commented Jul 28, 2026

Copy link
Copy Markdown
Member

Addresses Question 1 of #70587, and takes Question 2's parallel option.

A provider docs publish built two CI images from the same sources — the docs job inline at publish-docs-to-s3.yml:301-318 (python3.10) and the registry's own build-ci-image job (python3.12) — and only started the registry once the docs were already live on S3. Per #70587 the registry sub-workflow medians 23m, of which ~13m is that second image build, and it is 100% additive because it is strictly serial after the publish.

What changes

  • One image per run. A build-ci-image job in publish-docs-to-s3.yml builds from inputs.ref and stashes it; build-docs restores it instead of building inline, and update-registry passes ci-image-already-built: true so registry-build.yml skips its own build. A standalone registry dispatch has no such image and still builds one.
  • Both on one Python. default-python-version moves 3.10 → 3.12, which is what the registry has always extracted on. This removes the second image that differed only by interpreter. Note dev/registry/extract_metadata.py:47 and extract_versions.py:49 fall back to tomli below 3.11 — that path is # pragma: no cover, so 3.12 is the better-exercised side.
  • Docs and registry run side by side. update-registry moves from needs: [publish-docs-to-s3, build-info] to needs: [build-info, build-ci-image]. The registry reads nothing the docs publish produces.
  • The no-cache fallback is preserved, moved into the build step it belongs to. ci-image-build.yml gains a retry-without-cache input (default false, so every other caller is unaffected); when the cached build fails it retries once with DOCKER_CACHE=disabled. This keeps the behaviour of the inline breeze ci-image build || docker buildx build --pull it replaces, for old refs whose pyproject.toml has drifted from main.
  • Mount cache retained. Both image builds now set upload-mount-cache-artifact: "true", so the next publish on the branch restores it rather than re-downloading the dependency set.
  • Cache source unchangeddocker-cache: registry against branch: main, the cache the regular Test workflow keeps warm.

Resulting graph:

build-info -> build-ci-image -> { build-docs, update-registry }

Consequences worth reviewing

  1. The failed-docs gate is gone. needs: [publish-docs-to-s3] previously gated the registry, so a failed docs build skipped it. Running in parallel removes that: the registry can publish /stable/ links to docs that never landed. Get provider registry build off the docs publish critical path #70587 measures the exposure window at median ~3 min, up to ~21 min, negative on 5 of 11 sampled runs. If that is not acceptable, the gate should move onto the S3 sync step rather than back onto the whole job.
  2. The registry publish job re-asserts the committer allowlist. It previously inherited it by being needs: a job the allowlist skipped; now that job is also skipped on the shared-image path, so the check is stated explicitly on build-and-publish-registry.
  3. 3.10 → 3.12 is exercised by the registry but new for the docs build. Nothing in the docs path looked version-specific, but this is the change least verifiable without a real release run.

Not done here, and worth a separate look: restoring the canary image instead of building at all. It does not work through the stash — the Tests (AMD) run stashes only the default Python (ci-image-save-v3-linux_amd64-3.10-…), the stash name is branch-suffixed with no input to select a branch (current + base/default only), and artifacts expire in ~2 days. The cross-run-viable route would be pulling ghcr.io/apache/airflow/<branch>/ci/python<ver>:latest, which push-image-cache.yml publishes for every canary Python.


Was generative AI tooling used to co-author this PR?
  • Yes — Claude Code (Opus 5)

Generated-by: Claude Code (Opus 5) following the guidelines

potiuk added 3 commits July 28, 2026 14:08
A docs publish paid for two CI image builds of the same sources, one for
the docs and one for the provider registry, and only started the second
once the docs were already live. Building it once up front and handing the
same image to both lets them run side by side, which is most of the wait a
release manager sits through after the docs have landed.

Keeping the mount cache means consecutive publishes stop re-downloading the
whole dependency set, and pinning both to one Python removes the second
image that only differed by interpreter.
The build the docs job used to run inline fell back to a plain buildx build
whenever an old ref's pyproject.toml had drifted far enough from main that
main's registry cache could not satisfy it. Moving the build into its own
job dropped that safety net, which would turn a slow-but-correct build of
an old ref into a hard failure.
Expressing the no-cache retry as its own job put a permanently-skipped job
in every docs publish and forced both consumers to reason about which of
two builds produced the image. The retry belongs where the build is, and
callers that do not want it are unaffected because it is off by default.
@potiuk
potiuk merged commit c6449eb into main Jul 28, 2026
156 checks passed
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Airflow Registry Jul 28, 2026
@potiuk
potiuk deleted the share-ci-image-docs-registry branch July 28, 2026 19:19
@github-actions

Copy link
Copy Markdown
Contributor

Backport failed to create: v3-3-test. View the failure log Run details

Note: As of Merging PRs targeted for Airflow 3.X
the committer who merges the PR is responsible for backporting the PRs that are bug fixes (generally speaking) to the maintenance branches.

In matter of doubt please ask in #release-management Slack channel.

Status Branch Result
v3-3-test Commit Link

You can attempt to backport this manually by running:

cherry_picker c6449eb v3-3-test

This should apply the commit to the v3-3-test branch and leave the commit in conflict state marking
the files that need manual conflict resolution.

After you have resolved the conflicts, you can continue the backport process by running:

cherry_picker --continue

If you don't have cherry-picker installed, see the installation guide.

needs: [publish-docs-to-s3, build-info]
# Runs alongside the docs build rather than after it: the registry reads nothing the docs
# publish produces, so waiting only added its whole duration to the release manager's wait.
needs: [build-info, build-ci-image]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the gate question: the sync steps look like the cheap place for it. Measured on run 30335779728, everything the registry job does after the image is 4m30s, and only 1m21s of that is Sync registry to S3 plus Publish version metadata (which is host-side, no CI image needed). The site already goes up as the registry-site artifact, so those two steps could move into a job that needs publish-docs-to-s3, and the docs branch (~27m of Sphinx plus 4m publish) is long enough that the registry would be idle waiting for it anyway, so the wall clock does not change.

That also retires the #70587 caveat that the registry becomes the critical path on 5 of 11 runs. That was only true while it built its own image.

# cached against main's registry cache, which the regular Test workflow keeps warm.
checkout-ref: ${{ inputs.ref }}
push-image: "false"
upload-image-artifact: "true"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Splitting the build into its own job means the docs path now pays an image round-trip it did not before. On run 30335779728, Export CI docker image plus Stash CI docker image was 4m26s, the consumer side (Prepare breeze & CI image in the registry job, minus the breeze install and disk cleanup it shares) about 5m, and the new job repeats the checkout / free-disk / breeze preamble for another ~1m50s. For a provider wave that is a clear net win against a 13-32m second build, but build-ci-image has no if:, so an apache-airflow-only publish skips update-registry and pays the whole ~11m for a stash that only build-docs reads. Per #70587 that cohort medians 21m, so it is a large relative hit. Worth gating the job on registry-providers != '' || registry-full-build == 'true' and leaving the inline build in build-docs for that case?

# skipping that job used to enforce for us on a dispatch.
if: >
!cancelled() && needs.build-ci-image.result != 'failure' && (
github.event_name == 'workflow_call' ||

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

github.event_name in a called workflow is the caller's event (workflow_dispatch here), never workflow_call, so this disjunct never fires and the job ends up gated on github.event.sender.login on both paths. Same net behaviour as before, since the old needs: [build-ci-image] inherited the identical check, but it does mean this allowlist is load-bearing for the shared-image path and it has drifted from the one on build-info in publish-docs-to-s3.yml, which also lists vatsrahul1001. A wave dispatched by them would publish docs and silently skip the registry.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants