This is an automated email from the ASF dual-hosted git repository.

potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new d9d8fdb3a19 Speed up docs publishes by reusing the previously built CI 
image (#70650)
d9d8fdb3a19 is described below

commit d9d8fdb3a19390c8e047b274855af8a154843448
Author: Jarek Potiuk <[email protected]>
AuthorDate: Wed Jul 29 11:32:53 2026 +0200

    Speed up docs publishes by reusing the previously built CI image (#70650)
    
    Publishing docs for a ref cut before main pays for a from-scratch CI image
    build. The registry cache the build reads is main's, and the first
    Dockerfile.ci change made since the ref was cut breaks the cache chain at 
the
    third layer - #70285, which bakes Node.js and pnpm into the image, is by
    itself enough to make the twenty-minute Python build run again. Nothing
    caches those layers for the ref being documented except the image the
    previous publish of that same ref already built.
    
    That image could not serve as a cache until now. BuildKit imports the cache
    manifest of an image loaded from a tarball and then discards every record
    whose layers it cannot pull from a registry, so a docker load contributes
    nothing at all; serving the image from a registry on localhost is what makes
    its records usable. Recording those records in the first place needs inline
    cache metadata, which no image carried before.
---
 .github/workflows/ci-image-build.yml               |  78 ++++++++++++++++
 .github/workflows/publish-docs-to-s3.yml           |   7 ++
 dev/breeze/doc/images/output_ci-image_build.svg    | 104 ++++++++++++---------
 dev/breeze/doc/images/output_ci-image_build.txt    |   2 +-
 dev/breeze/doc/images/output_ci-image_load.svg     |  22 +++--
 dev/breeze/doc/images/output_ci-image_load.txt     |   2 +-
 .../airflow_breeze/commands/ci_image_commands.py   |  16 ++++
 .../commands/ci_image_commands_config.py           |   2 +
 .../commands/common_image_options.py               |  11 +++
 .../airflow_breeze/params/common_build_params.py   |  10 ++
 dev/breeze/tests/test_build_ci_params.py           |  57 +++++++++++
 11 files changed, 254 insertions(+), 57 deletions(-)

diff --git a/.github/workflows/ci-image-build.yml 
b/.github/workflows/ci-image-build.yml
index f9aaa3aa473..df116423717 100644
--- a/.github/workflows/ci-image-build.yml
+++ b/.github/workflows/ci-image-build.yml
@@ -103,6 +103,25 @@ on:  # yamllint disable-line rule:truthy
         description: "Disable airflow repo cache read from main."
         required: true
         type: string
+      seed-cache-from-stashed-image:
+        description: >
+          Seed the build cache from the CI image an earlier run stashed 
(true/false). Worth it
+          where the registry cache is built for a different branch than the 
sources - the
+          previous image matches them layer for layer, the registry cache of 
another branch may
+          match nothing.
+        required: false
+        default: "false"
+        type: string
+      image-stash-suffix:
+        description: >
+          Appended verbatim to the key of an extra copy of the image stash (so 
it needs its own
+          leading separator). Callers that build many different refs from one 
branch pass the ref
+          here to keep a copy per ref: they all share the plain per-branch 
stash and would
+          otherwise seed each other's builds, and refs far enough apart make 
poor caches for one
+          another. Seeding prefers this copy and falls back to the per-branch 
one.
+        required: false
+        default: ""
+        type: string
 permissions:
   contents: read
 jobs:
@@ -166,6 +185,44 @@ jobs:
           breeze ci-image import-mount-cache
           --cache-file 
/tmp/ci-cache-mount-save-v3-${PYTHON_MAJOR_MINOR_VERSION}.tar.gz
         if: steps.restore-cache-mount.outputs.stash-hit == 'true'
+      # BuildKit reads an image's recorded layer cache only when it can pull 
that image's layers
+      # from a registry - one merely loaded into the docker engine contributes 
nothing. So the
+      # image an earlier run stashed is served back to the build from a 
registry on localhost.
+      - name: >
+          Restore CI docker image for this ref
+          ${{ inputs.platform }}:${{ env.PYTHON_MAJOR_MINOR_VERSION }}
+        uses: 
apache/infrastructure-actions/stash/restore@49df447b39b18354895520e0a63731b7cad7cbec
+        with:
+          key: "ci-image-save-v3-${{ inputs.platform }}-${{ 
env.PYTHON_MAJOR_MINOR_VERSION }}\
+            ${{ inputs.image-stash-suffix }}"
+          path: "/mnt/"
+          only-current-branch: 'true'
+        id: restore-cache-image-for-ref
+        if: inputs.seed-cache-from-stashed-image == 'true' && 
inputs.image-stash-suffix != ''
+      - name: "Restore CI docker image ${{ inputs.platform }}:${{ 
env.PYTHON_MAJOR_MINOR_VERSION }}"
+        uses: 
apache/infrastructure-actions/stash/restore@49df447b39b18354895520e0a63731b7cad7cbec
+        with:
+          key: "ci-image-save-v3-${{ inputs.platform }}-${{ 
env.PYTHON_MAJOR_MINOR_VERSION }}"
+          path: "/mnt/"
+          only-current-branch: 'true'
+        id: restore-cache-image
+        if: >
+          inputs.seed-cache-from-stashed-image == 'true' &&
+          steps.restore-cache-image-for-ref.outputs.stash-hit != 'true'
+      - name: "Serve stashed image as cache ${{ inputs.platform }}:${{ 
env.PYTHON_MAJOR_MINOR_VERSION }}"
+        env:
+          PLATFORM: ${{ inputs.platform }}
+          CACHE_IMAGE: "localhost:5000/ci-image-cache:${{ 
env.PYTHON_MAJOR_MINOR_VERSION }}"
+        run: |
+          docker run -d --name cache-registry -p 5000:5000 registry:2
+          breeze ci-image load --platform "${PLATFORM}" --python 
"${PYTHON_MAJOR_MINOR_VERSION}" \
+            --image-file-dir "/mnt" --tag-as "${CACHE_IMAGE}"
+          docker push "${CACHE_IMAGE}"
+          echo "CACHE_FROM_IMAGE=${CACHE_IMAGE}" >> "${GITHUB_ENV}"
+        shell: bash
+        if: >
+          steps.restore-cache-image-for-ref.outputs.stash-hit == 'true' ||
+          steps.restore-cache-image.outputs.stash-hit == 'true'
       - name: "Login to ghcr.io"
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -203,6 +260,14 @@ jobs:
           PUSH: ${{ inputs.push-image }}
           VERBOSE: "true"
           PLATFORM: ${{ inputs.platform }}
+      - name: "Stop serving the cache image"
+        # The registry holds a second copy of a multi-gigabyte image and the 
export below needs
+        # the room.
+        run: |
+          docker rm -f cache-registry
+          docker rmi "${CACHE_FROM_IMAGE}"
+        shell: bash
+        if: always() && env.CACHE_FROM_IMAGE != ''
       - name: "Export CI docker image ${{ env.PYTHON_MAJOR_MINOR_VERSION }}"
         env:
           PLATFORM: ${{ inputs.platform }}
@@ -216,6 +281,19 @@ jobs:
           if-no-files-found: 'error'
           retention-days: '2'
         if: inputs.upload-image-artifact == 'true'
+      # Everything in this run reads the stash above; this copy exists only so 
the next run
+      # built from the same ref can seed its cache from sources it actually 
matches. It outlives
+      # that one by days - the same ref is rebuilt three days apart at the 
very least, so the
+      # two-day retention the shared stash gets would expire it before it is 
ever read.
+      - name: "Stash CI docker image for this ref ${{ 
env.PYTHON_MAJOR_MINOR_VERSION }}"
+        uses: 
apache/infrastructure-actions/stash/save@49df447b39b18354895520e0a63731b7cad7cbec
+        with:
+          key: "ci-image-save-v3-${{ inputs.platform }}-${{ 
env.PYTHON_MAJOR_MINOR_VERSION }}\
+            ${{ inputs.image-stash-suffix }}"
+          path: "/mnt/ci-image-save-*-${{ env.PYTHON_MAJOR_MINOR_VERSION 
}}.tar"
+          if-no-files-found: 'error'
+          retention-days: '6'
+        if: inputs.upload-image-artifact == 'true' && 
inputs.image-stash-suffix != ''
       - name: "Export mount cache ${{ inputs.platform }}:${{ 
env.PYTHON_MAJOR_MINOR_VERSION }}"
         env:
           PYTHON_MAJOR_MINOR_VERSION: ${{ env.PYTHON_MAJOR_MINOR_VERSION }}
diff --git a/.github/workflows/publish-docs-to-s3.yml 
b/.github/workflows/publish-docs-to-s3.yml
index 6345afa84b6..5f375004d1f 100644
--- a/.github/workflows/publish-docs-to-s3.yml
+++ b/.github/workflows/publish-docs-to-s3.yml
@@ -247,6 +247,13 @@ jobs:
       # ci-image-build.yml always restores it, so consecutive publishes stop 
re-downloading
       # the whole dependency set.
       upload-mount-cache-artifact: "true"
+      # Docs are published from refs cut days or weeks before main, and main's 
registry cache
+      # stops matching at the first Dockerfile.ci change made since the cut - 
#70285 alone
+      # costs a from-scratch Python build. The image the previous publish 
stashed was built
+      # from these very sources, so it is the cache main's cannot be. 
Publishes of different
+      # refs share one branch-scoped stash, hence the per-ref copy to prefer 
over it.
+      seed-cache-from-stashed-image: "true"
+      image-stash-suffix: "-${{ inputs.ref }}"
       python-versions: ${{ format('["{0}"]', 
needs.build-info.outputs.default-python-version) }}
       branch: "main"
       constraints-branch: "constraints-main"
diff --git a/dev/breeze/doc/images/output_ci-image_build.svg 
b/dev/breeze/doc/images/output_ci-image_build.svg
index f58ee2f3031..1f020988b46 100644
--- a/dev/breeze/doc/images/output_ci-image_build.svg
+++ b/dev/breeze/doc/images/output_ci-image_build.svg
@@ -1,4 +1,4 @@
-<svg class="rich-terminal" viewBox="0 0 1482 1953.1999999999998" 
xmlns="http://www.w3.org/2000/svg";>
+<svg class="rich-terminal" viewBox="0 0 1482 2026.3999999999999" 
xmlns="http://www.w3.org/2000/svg";>
     <!-- Generated with Rich https://www.textualize.io -->
     <style>
 
@@ -43,7 +43,7 @@
 
     <defs>
     <clipPath id="breeze-ci-image-build-clip-terminal">
-      <rect x="0" y="0" width="1463.0" height="1902.1999999999998" />
+      <rect x="0" y="0" width="1463.0" height="1975.3999999999999" />
     </clipPath>
     <clipPath id="breeze-ci-image-build-line-0">
     <rect x="0" y="1.5" width="1464" height="24.65"/>
@@ -276,9 +276,18 @@
 <clipPath id="breeze-ci-image-build-line-76">
     <rect x="0" y="1855.9" width="1464" height="24.65"/>
             </clipPath>
+<clipPath id="breeze-ci-image-build-line-77">
+    <rect x="0" y="1880.3" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-ci-image-build-line-78">
+    <rect x="0" y="1904.7" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-ci-image-build-line-79">
+    <rect x="0" y="1929.1" width="1464" height="24.65"/>
+            </clipPath>
     </defs>
 
-    <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" 
x="1" y="1" width="1480" height="1951.2" rx="8"/><text 
class="breeze-ci-image-build-title" fill="#c5c8c6" text-anchor="middle" x="740" 
y="27">Command:&#160;ci-image&#160;build</text>
+    <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" 
x="1" y="1" width="1480" height="2024.4" rx="8"/><text 
class="breeze-ci-image-build-title" fill="#c5c8c6" text-anchor="middle" x="740" 
y="27">Command:&#160;ci-image&#160;build</text>
             <g transform="translate(26,22)">
             <circle cx="0" cy="0" r="7" fill="#ff5f57"/>
             <circle cx="22" cy="0" r="7" fill="#febc2e"/>
@@ -323,49 +332,52 @@
 </text><text class="breeze-ci-image-build-r5" x="0" y="800.8" 
textLength="24.4" 
clip-path="url(#breeze-ci-image-build-line-32)">╭─</text><text 
class="breeze-ci-image-build-r5" x="24.4" y="800.8" textLength="512.4" 
clip-path="url(#breeze-ci-image-build-line-32)">&#160;Advanced&#160;build&#160;options&#160;(for&#160;power&#160;users)&#160;</text><text
 class="breeze-ci-image-build-r5" x="536.8" y="800.8" textLength="902.8" 
clip-path="url(#breeze-ci-image-build-line-32)">──────────────────── [...]
 </text><text class="breeze-ci-image-build-r5" x="0" y="825.2" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-33)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="825.2" textLength="366" 
clip-path="url(#breeze-ci-image-build-line-33)">--additional-pip-install-flags</text><text
 class="breeze-ci-image-build-r1" x="414.8" y="825.2" textLength="1024.8" 
clip-path="url(#breeze-ci-image-build-line-33)">Additional&#160;flags&#160;added&#160;to&#160;`pip&#160;install`&#160
 [...]
 </text><text class="breeze-ci-image-build-r5" x="0" y="849.6" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-34)">│</text><text 
class="breeze-ci-image-build-r7" x="414.8" y="849.6" textLength="73.2" 
clip-path="url(#breeze-ci-image-build-line-34)">(TEXT)</text><text 
class="breeze-ci-image-build-r5" x="1451.8" y="849.6" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-34)">│</text><text 
class="breeze-ci-image-build-r1" x="1464" y="849.6" textLength="12.2" clip-pat 
[...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="874" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-35)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="874" textLength="366" 
clip-path="url(#breeze-ci-image-build-line-35)">--commit-sha&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-ci-image-build-r1" x="414.8" y="874" textLength="549" 
clip-path="url(#breeze-ci-image-buil [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="898.4" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-36)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="898.4" textLength="366" 
clip-path="url(#breeze-ci-image-build-line-36)">--debian-version&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-ci-image-build-r1" x="414.8" y="898.4" textLength="805.2" 
clip-path="url(#breeze-ci-image-build-line-36)"> [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="922.8" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-37)">│</text><text 
class="breeze-ci-image-build-r5" x="414.8" y="922.8" textLength="109.8" 
clip-path="url(#breeze-ci-image-build-line-37)">bookworm]</text><text 
class="breeze-ci-image-build-r7" x="536.8" y="922.8" textLength="122" 
clip-path="url(#breeze-ci-image-build-line-37)">(bookworm)</text><text 
class="breeze-ci-image-build-r5" x="1451.8" y="922.8" textLength="1 [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="947.2" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-38)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="947.2" textLength="366" 
clip-path="url(#breeze-ci-image-build-line-38)">--disable-airflow-repo-cache&#160;&#160;</text><text
 class="breeze-ci-image-build-r1" x="414.8" y="947.2" textLength="658.8" 
clip-path="url(#breeze-ci-image-build-line-38)">Disable&#160;cache&#160;from&#160;Airflow&#160;repository&#1
 [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="971.6" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-39)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="971.6" textLength="366" 
clip-path="url(#breeze-ci-image-build-line-39)">--install-mysql-client-type&#160;&#160;&#160;</text><text
 class="breeze-ci-image-build-r1" x="414.8" y="971.6" textLength="488" 
clip-path="url(#breeze-ci-image-build-line-39)">Which&#160;client&#160;to&#160;choose&#160;when&#160;inst
 [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="996" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-40)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="996" textLength="366" 
clip-path="url(#breeze-ci-image-build-line-40)">--python-image&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-ci-image-build-r1" x="414.8" y="996" textLength="1024.8" 
clip-path="url(#breeze-ci-image-build-line- [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="1020.4" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-41)">│</text><text 
class="breeze-ci-image-build-r1" x="414.8" y="1020.4" textLength="561.2" 
clip-path="url(#breeze-ci-image-build-line-41)">something&#160;like:&#160;python:VERSION-slim-bookworm.&#160;</text><text
 class="breeze-ci-image-build-r7" x="976" y="1020.4" textLength="73.2" 
clip-path="url(#breeze-ci-image-build-line-41)">(TEXT)</text><text 
class="breeze-ci- [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="1044.8" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-42)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1044.8" textLength="97.6" 
clip-path="url(#breeze-ci-image-build-line-42)">--use-uv</text><text 
class="breeze-ci-image-build-r1" x="122" y="1044.8" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-42)">/</text><text 
class="breeze-ci-image-build-r4" x="134.2" y="1044.8" textLength="134.2" clip 
[...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="1069.2" 
textLength="1464" 
clip-path="url(#breeze-ci-image-build-line-43)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-ci-image-build-r1" x="1464" y="1069.2" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-43)">
-</text><text class="breeze-ci-image-build-r5" x="0" y="1093.6" 
textLength="24.4" 
clip-path="url(#breeze-ci-image-build-line-44)">╭─</text><text 
class="breeze-ci-image-build-r5" x="24.4" y="1093.6" textLength="597.8" 
clip-path="url(#breeze-ci-image-build-line-44)">&#160;Selecting&#160;constraint&#160;location&#160;(for&#160;power&#160;users)&#160;</text><text
 class="breeze-ci-image-build-r5" x="622.2" y="1093.6" textLength="817.4" 
clip-path="url(#breeze-ci-image-build-line-44)">────────── [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="1118" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-45)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1118" textLength="378.2" 
clip-path="url(#breeze-ci-image-build-line-45)">--airflow-constraints-location&#160;</text><text
 class="breeze-ci-image-build-r1" x="427" y="1118" textLength="915" 
clip-path="url(#breeze-ci-image-build-line-45)">Location&#160;of&#160;airflow&#160;constraints&#160;to&#160;use&#160;
 [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="1142.4" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-46)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1142.4" textLength="378.2" 
clip-path="url(#breeze-ci-image-build-line-46)">--airflow-constraints-mode&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-ci-image-build-r1" x="427" y="1142.4" textLength="671" 
clip-path="url(#breeze-ci-image-build-line-46)">Mode&#160;of&#160;constraints&#160;for&#160
 [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="1166.8" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-47)">│</text><text 
class="breeze-ci-image-build-r5" x="427" y="1166.8" textLength="353.8" 
clip-path="url(#breeze-ci-image-build-line-47)">constraints-source-providers]</text><text
 class="breeze-ci-image-build-r7" x="793" y="1166.8" textLength="561.2" 
clip-path="url(#breeze-ci-image-build-line-47)">(constraints-source-providers&#160;|&#160;constraints&#160;|&#160;</t
 [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="1191.2" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-48)">│</text><text 
class="breeze-ci-image-build-r7" x="427" y="1191.2" textLength="305" 
clip-path="url(#breeze-ci-image-build-line-48)">constraints-no-providers)</text><text
 class="breeze-ci-image-build-r5" x="1451.8" y="1191.2" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-48)">│</text><text 
class="breeze-ci-image-build-r1" x="1464" y="1191.2" textLe [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="1215.6" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-49)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1215.6" textLength="378.2" 
clip-path="url(#breeze-ci-image-build-line-49)">--airflow-constraints-reference</text><text
 class="breeze-ci-image-build-r1" x="427" y="1215.6" textLength="646.6" 
clip-path="url(#breeze-ci-image-build-line-49)">Constraint&#160;reference&#160;to&#160;use&#160;when&#160;building
 [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="1240" textLength="1464" 
clip-path="url(#breeze-ci-image-build-line-50)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-ci-image-build-r1" x="1464" y="1240" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-50)">
-</text><text class="breeze-ci-image-build-r5" x="0" y="1264.4" 
textLength="24.4" 
clip-path="url(#breeze-ci-image-build-line-51)">╭─</text><text 
class="breeze-ci-image-build-r5" x="24.4" y="1264.4" textLength="634.4" 
clip-path="url(#breeze-ci-image-build-line-51)">&#160;Choosing&#160;dependencies&#160;and&#160;extras&#160;(for&#160;power&#160;users)&#160;</text><text
 class="breeze-ci-image-build-r5" x="658.8" y="1264.4" textLength="780.8" 
clip-path="url(#breeze-ci-image-build-line-51)">── [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="1288.8" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-52)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1288.8" textLength="341.6" 
clip-path="url(#breeze-ci-image-build-line-52)">--additional-airflow-extras&#160;</text><text
 class="breeze-ci-image-build-r1" x="390.4" y="1288.8" textLength="780.8" 
clip-path="url(#breeze-ci-image-build-line-52)">Additional&#160;extra&#160;package&#160;while&#160;installing&
 [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="1313.2" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-53)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1313.2" textLength="341.6" 
clip-path="url(#breeze-ci-image-build-line-53)">--additional-python-deps&#160;&#160;&#160;&#160;</text><text
 class="breeze-ci-image-build-r1" x="390.4" y="1313.2" textLength="780.8" 
clip-path="url(#breeze-ci-image-build-line-53)">Additional&#160;python&#160;dependencies&#160;t
 [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="1337.6" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-54)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1337.6" textLength="341.6" 
clip-path="url(#breeze-ci-image-build-line-54)">--dev-apt-deps&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-ci-image-build-r1" x="390.4" y="1337.6" textLength="658.8" 
clip-path="url(#breeze-ci-image-build-line-54 [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="1362" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-55)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1362" textLength="341.6" 
clip-path="url(#breeze-ci-image-build-line-55)">--additional-dev-apt-deps&#160;&#160;&#160;</text><text
 class="breeze-ci-image-build-r1" x="390.4" y="1362" textLength="793" 
clip-path="url(#breeze-ci-image-build-line-55)">Additional&#160;apt&#160;dev&#160;dependencies&#160;to&#160;
 [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="1386.4" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-56)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1386.4" textLength="341.6" 
clip-path="url(#breeze-ci-image-build-line-56)">--dev-apt-command&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-ci-image-build-r1" x="390.4" y="1386.4" textLength="634.4" 
clip-path="url(#breeze-ci-image-build-line-56)">Command&#160 [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="1410.8" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-57)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1410.8" textLength="341.6" 
clip-path="url(#breeze-ci-image-build-line-57)">--additional-dev-apt-command</text><text
 class="breeze-ci-image-build-r1" x="390.4" y="1410.8" textLength="768.6" 
clip-path="url(#breeze-ci-image-build-line-57)">Additional&#160;command&#160;executed&#160;before&#160;dev&#160;apt
 [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="1435.2" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-58)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1435.2" textLength="341.6" 
clip-path="url(#breeze-ci-image-build-line-58)">--additional-dev-apt-env&#160;&#160;&#160;&#160;</text><text
 class="breeze-ci-image-build-r1" x="390.4" y="1435.2" textLength="817.4" 
clip-path="url(#breeze-ci-image-build-line-58)">Additional&#160;environment&#160;variables&#160
 [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="1459.6" 
textLength="1464" 
clip-path="url(#breeze-ci-image-build-line-59)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-ci-image-build-r1" x="1464" y="1459.6" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-59)">
-</text><text class="breeze-ci-image-build-r5" x="0" y="1484" textLength="24.4" 
clip-path="url(#breeze-ci-image-build-line-60)">╭─</text><text 
class="breeze-ci-image-build-r5" x="24.4" y="1484" textLength="622.2" 
clip-path="url(#breeze-ci-image-build-line-60)">&#160;Preparing&#160;cache&#160;and&#160;push&#160;(for&#160;maintainers&#160;and&#160;CI)&#160;</text><text
 class="breeze-ci-image-build-r5" x="646.6" y="1484" textLength="793" 
clip-path="url(#breeze-ci-image-build-line-60)">────── [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="1508.4" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-61)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1508.4" textLength="268.4" 
clip-path="url(#breeze-ci-image-build-line-61)">--builder&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-ci-image-build-r1" x="317.2" y="1508.4" textLength="768.6" 
clip-path="url(#breeze-ci-image-build-line-61)">Buildx&# [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="1532.8" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-62)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1532.8" textLength="268.4" 
clip-path="url(#breeze-ci-image-build-line-62)">--platform&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-ci-image-build-r1" x="317.2" y="1532.8" textLength="341.6" 
clip-path="url(#breeze-ci-image-build-line-62)">Platform&#160 [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="1557.2" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-63)">│</text><text 
class="breeze-ci-image-build-r7" x="317.2" y="1557.2" textLength="292.8" 
clip-path="url(#breeze-ci-image-build-line-63)">linux/amd64,linux/arm64)</text><text
 class="breeze-ci-image-build-r5" x="1451.8" y="1557.2" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-63)">│</text><text 
class="breeze-ci-image-build-r1" x="1464" y="1557.2" tex [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="1581.6" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-64)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1581.6" textLength="268.4" 
clip-path="url(#breeze-ci-image-build-line-64)">--push&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-ci-image-build-r1" x="317.2" y="1581.6" textLength="353.8" 
clip-path="url(#breeze-ci-image-build-lin [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="1606" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-65)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1606" textLength="268.4" 
clip-path="url(#breeze-ci-image-build-line-65)">--prepare-buildx-cache</text><text
 class="breeze-ci-image-build-r1" x="317.2" y="1606" textLength="1122.4" 
clip-path="url(#breeze-ci-image-build-line-65)">Prepares&#160;build&#160;cache&#160;(this&#160;is&#160;done&#160;as&#160;separ
 [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="1630.4" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-66)">│</text><text 
class="breeze-ci-image-build-r1" x="317.2" y="1630.4" textLength="1122.4" 
clip-path="url(#breeze-ci-image-build-line-66)">image).&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#16
 [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="1654.8" 
textLength="1464" 
clip-path="url(#breeze-ci-image-build-line-67)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-ci-image-build-r1" x="1464" y="1654.8" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-67)">
-</text><text class="breeze-ci-image-build-r5" x="0" y="1679.2" 
textLength="24.4" 
clip-path="url(#breeze-ci-image-build-line-68)">╭─</text><text 
class="breeze-ci-image-build-r5" x="24.4" y="1679.2" textLength="280.6" 
clip-path="url(#breeze-ci-image-build-line-68)">&#160;GitHub&#160;authentication&#160;</text><text
 class="breeze-ci-image-build-r5" x="305" y="1679.2" textLength="1134.6" 
clip-path="url(#breeze-ci-image-build-line-68)">─────────────────────────────────────────────────────────
 [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="1703.6" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-69)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1703.6" textLength="231.8" 
clip-path="url(#breeze-ci-image-build-line-69)">--github-repository</text><text 
class="breeze-ci-image-build-r6" x="280.6" y="1703.6" textLength="24.4" 
clip-path="url(#breeze-ci-image-build-line-69)">-g</text><text 
class="breeze-ci-image-build-r1" x="329.4" y="1703.6" textLeng [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="1728" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-70)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1728" textLength="231.8" 
clip-path="url(#breeze-ci-image-build-line-70)">--github-token&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-ci-image-build-r1" x="329.4" y="1728" textLength="512.4" 
clip-path="url(#breeze-ci-image-build-line-70)">The&#160;token&#160;used&#160;to&#160;authenticate&#160;t
 [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="1752.4" 
textLength="1464" 
clip-path="url(#breeze-ci-image-build-line-71)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-ci-image-build-r1" x="1464" y="1752.4" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-71)">
-</text><text class="breeze-ci-image-build-r5" x="0" y="1776.8" 
textLength="24.4" 
clip-path="url(#breeze-ci-image-build-line-72)">╭─</text><text 
class="breeze-ci-image-build-r5" x="24.4" y="1776.8" textLength="195.2" 
clip-path="url(#breeze-ci-image-build-line-72)">&#160;Common&#160;options&#160;</text><text
 class="breeze-ci-image-build-r5" x="219.6" y="1776.8" textLength="1220" 
clip-path="url(#breeze-ci-image-build-line-72)">────────────────────────────────────────────────────────────────
 [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="1801.2" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-73)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1801.2" textLength="109.8" 
clip-path="url(#breeze-ci-image-build-line-73)">--answer&#160;</text><text 
class="breeze-ci-image-build-r6" x="158.6" y="1801.2" textLength="24.4" 
clip-path="url(#breeze-ci-image-build-line-73)">-a</text><text 
class="breeze-ci-image-build-r1" x="207.4" y="1801.2" textLength="3 [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="1825.6" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-74)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1825.6" textLength="109.8" 
clip-path="url(#breeze-ci-image-build-line-74)">--dry-run</text><text 
class="breeze-ci-image-build-r6" x="158.6" y="1825.6" textLength="24.4" 
clip-path="url(#breeze-ci-image-build-line-74)">-D</text><text 
class="breeze-ci-image-build-r1" x="207.4" y="1825.6" textLength="719.8" [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="1850" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-75)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1850" textLength="109.8" 
clip-path="url(#breeze-ci-image-build-line-75)">--verbose</text><text 
class="breeze-ci-image-build-r6" x="158.6" y="1850" textLength="24.4" 
clip-path="url(#breeze-ci-image-build-line-75)">-v</text><text 
class="breeze-ci-image-build-r1" x="207.4" y="1850" textLength="585.6" clip-pa 
[...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="1874.4" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-76)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1874.4" textLength="109.8" 
clip-path="url(#breeze-ci-image-build-line-76)">--help&#160;&#160;&#160;</text><text
 class="breeze-ci-image-build-r6" x="158.6" y="1874.4" textLength="24.4" 
clip-path="url(#breeze-ci-image-build-line-76)">-h</text><text 
class="breeze-ci-image-build-r1" x="207.4" y="1874.4" tex [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="1898.8" 
textLength="1464" 
clip-path="url(#breeze-ci-image-build-line-77)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-ci-image-build-r1" x="1464" y="1898.8" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-77)">
+</text><text class="breeze-ci-image-build-r5" x="0" y="874" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-35)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="874" textLength="366" 
clip-path="url(#breeze-ci-image-build-line-35)">--cache-from-image&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-ci-image-build-r1" x="414.8" y="874" textLength="1024.8" 
clip-path="url(#breeze-ci-image-build-line-35)">Additional&#160 [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="898.4" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-36)">│</text><text 
class="breeze-ci-image-build-r1" x="414.8" y="898.4" textLength="1024.8" 
clip-path="url(#breeze-ci-image-build-line-36)">registry,&#160;an&#160;image&#160;only&#160;present&#160;in&#160;the&#160;local&#160;docker&#160;engine&#160;contributes&#160;no&#160;cache.&#160;&#160;&#160;&#160;</text><text
 class="breeze-ci-image-build-r5" x="1451.8" y="898.4 [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="922.8" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-37)">│</text><text 
class="breeze-ci-image-build-r7" x="414.8" y="922.8" textLength="73.2" 
clip-path="url(#breeze-ci-image-build-line-37)">(TEXT)</text><text 
class="breeze-ci-image-build-r5" x="1451.8" y="922.8" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-37)">│</text><text 
class="breeze-ci-image-build-r1" x="1464" y="922.8" textLength="12.2" clip-pat 
[...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="947.2" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-38)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="947.2" textLength="366" 
clip-path="url(#breeze-ci-image-build-line-38)">--commit-sha&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-ci-image-build-r1" x="414.8" y="947.2" textLength="549" 
clip-path="url(#breeze-ci-imag [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="971.6" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-39)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="971.6" textLength="366" 
clip-path="url(#breeze-ci-image-build-line-39)">--debian-version&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-ci-image-build-r1" x="414.8" y="971.6" textLength="805.2" 
clip-path="url(#breeze-ci-image-build-line-39)"> [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="996" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-40)">│</text><text 
class="breeze-ci-image-build-r5" x="414.8" y="996" textLength="109.8" 
clip-path="url(#breeze-ci-image-build-line-40)">bookworm]</text><text 
class="breeze-ci-image-build-r7" x="536.8" y="996" textLength="122" 
clip-path="url(#breeze-ci-image-build-line-40)">(bookworm)</text><text 
class="breeze-ci-image-build-r5" x="1451.8" y="996" textLength="12.2" cli [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="1020.4" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-41)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1020.4" textLength="366" 
clip-path="url(#breeze-ci-image-build-line-41)">--disable-airflow-repo-cache&#160;&#160;</text><text
 class="breeze-ci-image-build-r1" x="414.8" y="1020.4" textLength="658.8" 
clip-path="url(#breeze-ci-image-build-line-41)">Disable&#160;cache&#160;from&#160;Airflow&#160;repository
 [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="1044.8" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-42)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1044.8" textLength="366" 
clip-path="url(#breeze-ci-image-build-line-42)">--install-mysql-client-type&#160;&#160;&#160;</text><text
 class="breeze-ci-image-build-r1" x="414.8" y="1044.8" textLength="488" 
clip-path="url(#breeze-ci-image-build-line-42)">Which&#160;client&#160;to&#160;choose&#160;when&#160;i
 [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="1069.2" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-43)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1069.2" textLength="366" 
clip-path="url(#breeze-ci-image-build-line-43)">--python-image&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-ci-image-build-r1" x="414.8" y="1069.2" textLength="1024.8" 
clip-path="url(#breeze-ci-image-bu [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="1093.6" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-44)">│</text><text 
class="breeze-ci-image-build-r1" x="414.8" y="1093.6" textLength="561.2" 
clip-path="url(#breeze-ci-image-build-line-44)">something&#160;like:&#160;python:VERSION-slim-bookworm.&#160;</text><text
 class="breeze-ci-image-build-r7" x="976" y="1093.6" textLength="73.2" 
clip-path="url(#breeze-ci-image-build-line-44)">(TEXT)</text><text 
class="breeze-ci- [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="1118" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-45)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1118" textLength="97.6" 
clip-path="url(#breeze-ci-image-build-line-45)">--use-uv</text><text 
class="breeze-ci-image-build-r1" x="122" y="1118" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-45)">/</text><text 
class="breeze-ci-image-build-r4" x="134.2" y="1118" textLength="134.2" 
clip-path="u [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="1142.4" 
textLength="1464" 
clip-path="url(#breeze-ci-image-build-line-46)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-ci-image-build-r1" x="1464" y="1142.4" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-46)">
+</text><text class="breeze-ci-image-build-r5" x="0" y="1166.8" 
textLength="24.4" 
clip-path="url(#breeze-ci-image-build-line-47)">╭─</text><text 
class="breeze-ci-image-build-r5" x="24.4" y="1166.8" textLength="597.8" 
clip-path="url(#breeze-ci-image-build-line-47)">&#160;Selecting&#160;constraint&#160;location&#160;(for&#160;power&#160;users)&#160;</text><text
 class="breeze-ci-image-build-r5" x="622.2" y="1166.8" textLength="817.4" 
clip-path="url(#breeze-ci-image-build-line-47)">────────── [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="1191.2" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-48)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1191.2" textLength="378.2" 
clip-path="url(#breeze-ci-image-build-line-48)">--airflow-constraints-location&#160;</text><text
 class="breeze-ci-image-build-r1" x="427" y="1191.2" textLength="915" 
clip-path="url(#breeze-ci-image-build-line-48)">Location&#160;of&#160;airflow&#160;constraints&#160;to&#160;use
 [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="1215.6" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-49)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1215.6" textLength="378.2" 
clip-path="url(#breeze-ci-image-build-line-49)">--airflow-constraints-mode&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-ci-image-build-r1" x="427" y="1215.6" textLength="671" 
clip-path="url(#breeze-ci-image-build-line-49)">Mode&#160;of&#160;constraints&#160;for&#160
 [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="1240" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-50)">│</text><text 
class="breeze-ci-image-build-r5" x="427" y="1240" textLength="353.8" 
clip-path="url(#breeze-ci-image-build-line-50)">constraints-source-providers]</text><text
 class="breeze-ci-image-build-r7" x="793" y="1240" textLength="561.2" 
clip-path="url(#breeze-ci-image-build-line-50)">(constraints-source-providers&#160;|&#160;constraints&#160;|&#160;</text><t
 [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="1264.4" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-51)">│</text><text 
class="breeze-ci-image-build-r7" x="427" y="1264.4" textLength="305" 
clip-path="url(#breeze-ci-image-build-line-51)">constraints-no-providers)</text><text
 class="breeze-ci-image-build-r5" x="1451.8" y="1264.4" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-51)">│</text><text 
class="breeze-ci-image-build-r1" x="1464" y="1264.4" textLe [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="1288.8" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-52)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1288.8" textLength="378.2" 
clip-path="url(#breeze-ci-image-build-line-52)">--airflow-constraints-reference</text><text
 class="breeze-ci-image-build-r1" x="427" y="1288.8" textLength="646.6" 
clip-path="url(#breeze-ci-image-build-line-52)">Constraint&#160;reference&#160;to&#160;use&#160;when&#160;building
 [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="1313.2" 
textLength="1464" 
clip-path="url(#breeze-ci-image-build-line-53)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-ci-image-build-r1" x="1464" y="1313.2" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-53)">
+</text><text class="breeze-ci-image-build-r5" x="0" y="1337.6" 
textLength="24.4" 
clip-path="url(#breeze-ci-image-build-line-54)">╭─</text><text 
class="breeze-ci-image-build-r5" x="24.4" y="1337.6" textLength="634.4" 
clip-path="url(#breeze-ci-image-build-line-54)">&#160;Choosing&#160;dependencies&#160;and&#160;extras&#160;(for&#160;power&#160;users)&#160;</text><text
 class="breeze-ci-image-build-r5" x="658.8" y="1337.6" textLength="780.8" 
clip-path="url(#breeze-ci-image-build-line-54)">── [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="1362" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-55)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1362" textLength="341.6" 
clip-path="url(#breeze-ci-image-build-line-55)">--additional-airflow-extras&#160;</text><text
 class="breeze-ci-image-build-r1" x="390.4" y="1362" textLength="780.8" 
clip-path="url(#breeze-ci-image-build-line-55)">Additional&#160;extra&#160;package&#160;while&#160;installing&#160;A
 [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="1386.4" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-56)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1386.4" textLength="341.6" 
clip-path="url(#breeze-ci-image-build-line-56)">--additional-python-deps&#160;&#160;&#160;&#160;</text><text
 class="breeze-ci-image-build-r1" x="390.4" y="1386.4" textLength="780.8" 
clip-path="url(#breeze-ci-image-build-line-56)">Additional&#160;python&#160;dependencies&#160;t
 [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="1410.8" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-57)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1410.8" textLength="341.6" 
clip-path="url(#breeze-ci-image-build-line-57)">--dev-apt-deps&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-ci-image-build-r1" x="390.4" y="1410.8" textLength="658.8" 
clip-path="url(#breeze-ci-image-build-line-57 [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="1435.2" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-58)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1435.2" textLength="341.6" 
clip-path="url(#breeze-ci-image-build-line-58)">--additional-dev-apt-deps&#160;&#160;&#160;</text><text
 class="breeze-ci-image-build-r1" x="390.4" y="1435.2" textLength="793" 
clip-path="url(#breeze-ci-image-build-line-58)">Additional&#160;apt&#160;dev&#160;dependencies&#160;to
 [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="1459.6" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-59)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1459.6" textLength="341.6" 
clip-path="url(#breeze-ci-image-build-line-59)">--dev-apt-command&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-ci-image-build-r1" x="390.4" y="1459.6" textLength="634.4" 
clip-path="url(#breeze-ci-image-build-line-59)">Command&#160 [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="1484" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-60)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1484" textLength="341.6" 
clip-path="url(#breeze-ci-image-build-line-60)">--additional-dev-apt-command</text><text
 class="breeze-ci-image-build-r1" x="390.4" y="1484" textLength="768.6" 
clip-path="url(#breeze-ci-image-build-line-60)">Additional&#160;command&#160;executed&#160;before&#160;dev&#160;apt&#160;
 [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="1508.4" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-61)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1508.4" textLength="341.6" 
clip-path="url(#breeze-ci-image-build-line-61)">--additional-dev-apt-env&#160;&#160;&#160;&#160;</text><text
 class="breeze-ci-image-build-r1" x="390.4" y="1508.4" textLength="817.4" 
clip-path="url(#breeze-ci-image-build-line-61)">Additional&#160;environment&#160;variables&#160
 [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="1532.8" 
textLength="1464" 
clip-path="url(#breeze-ci-image-build-line-62)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-ci-image-build-r1" x="1464" y="1532.8" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-62)">
+</text><text class="breeze-ci-image-build-r5" x="0" y="1557.2" 
textLength="24.4" 
clip-path="url(#breeze-ci-image-build-line-63)">╭─</text><text 
class="breeze-ci-image-build-r5" x="24.4" y="1557.2" textLength="622.2" 
clip-path="url(#breeze-ci-image-build-line-63)">&#160;Preparing&#160;cache&#160;and&#160;push&#160;(for&#160;maintainers&#160;and&#160;CI)&#160;</text><text
 class="breeze-ci-image-build-r5" x="646.6" y="1557.2" textLength="793" 
clip-path="url(#breeze-ci-image-build-line-63)"> [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="1581.6" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-64)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1581.6" textLength="268.4" 
clip-path="url(#breeze-ci-image-build-line-64)">--builder&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-ci-image-build-r1" x="317.2" y="1581.6" textLength="768.6" 
clip-path="url(#breeze-ci-image-build-line-64)">Buildx&# [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="1606" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-65)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1606" textLength="268.4" 
clip-path="url(#breeze-ci-image-build-line-65)">--platform&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-ci-image-build-r1" x="317.2" y="1606" textLength="341.6" 
clip-path="url(#breeze-ci-image-build-line-65)">Platform&#160;for&# [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="1630.4" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-66)">│</text><text 
class="breeze-ci-image-build-r7" x="317.2" y="1630.4" textLength="292.8" 
clip-path="url(#breeze-ci-image-build-line-66)">linux/amd64,linux/arm64)</text><text
 class="breeze-ci-image-build-r5" x="1451.8" y="1630.4" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-66)">│</text><text 
class="breeze-ci-image-build-r1" x="1464" y="1630.4" tex [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="1654.8" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-67)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1654.8" textLength="268.4" 
clip-path="url(#breeze-ci-image-build-line-67)">--push&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-ci-image-build-r1" x="317.2" y="1654.8" textLength="353.8" 
clip-path="url(#breeze-ci-image-build-lin [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="1679.2" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-68)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1679.2" textLength="268.4" 
clip-path="url(#breeze-ci-image-build-line-68)">--prepare-buildx-cache</text><text
 class="breeze-ci-image-build-r1" x="317.2" y="1679.2" textLength="1122.4" 
clip-path="url(#breeze-ci-image-build-line-68)">Prepares&#160;build&#160;cache&#160;(this&#160;is&#160;done&#160;as&#160
 [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="1703.6" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-69)">│</text><text 
class="breeze-ci-image-build-r1" x="317.2" y="1703.6" textLength="1122.4" 
clip-path="url(#breeze-ci-image-build-line-69)">image).&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#16
 [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="1728" textLength="1464" 
clip-path="url(#breeze-ci-image-build-line-70)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-ci-image-build-r1" x="1464" y="1728" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-70)">
+</text><text class="breeze-ci-image-build-r5" x="0" y="1752.4" 
textLength="24.4" 
clip-path="url(#breeze-ci-image-build-line-71)">╭─</text><text 
class="breeze-ci-image-build-r5" x="24.4" y="1752.4" textLength="280.6" 
clip-path="url(#breeze-ci-image-build-line-71)">&#160;GitHub&#160;authentication&#160;</text><text
 class="breeze-ci-image-build-r5" x="305" y="1752.4" textLength="1134.6" 
clip-path="url(#breeze-ci-image-build-line-71)">─────────────────────────────────────────────────────────
 [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="1776.8" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-72)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1776.8" textLength="231.8" 
clip-path="url(#breeze-ci-image-build-line-72)">--github-repository</text><text 
class="breeze-ci-image-build-r6" x="280.6" y="1776.8" textLength="24.4" 
clip-path="url(#breeze-ci-image-build-line-72)">-g</text><text 
class="breeze-ci-image-build-r1" x="329.4" y="1776.8" textLeng [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="1801.2" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-73)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1801.2" textLength="231.8" 
clip-path="url(#breeze-ci-image-build-line-73)">--github-token&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-ci-image-build-r1" x="329.4" y="1801.2" textLength="512.4" 
clip-path="url(#breeze-ci-image-build-line-73)">The&#160;token&#160;used&#160;to&#160;authenticate&
 [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="1825.6" 
textLength="1464" 
clip-path="url(#breeze-ci-image-build-line-74)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-ci-image-build-r1" x="1464" y="1825.6" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-74)">
+</text><text class="breeze-ci-image-build-r5" x="0" y="1850" textLength="24.4" 
clip-path="url(#breeze-ci-image-build-line-75)">╭─</text><text 
class="breeze-ci-image-build-r5" x="24.4" y="1850" textLength="195.2" 
clip-path="url(#breeze-ci-image-build-line-75)">&#160;Common&#160;options&#160;</text><text
 class="breeze-ci-image-build-r5" x="219.6" y="1850" textLength="1220" 
clip-path="url(#breeze-ci-image-build-line-75)">──────────────────────────────────────────────────────────────────────
 [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="1874.4" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-76)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1874.4" textLength="109.8" 
clip-path="url(#breeze-ci-image-build-line-76)">--answer&#160;</text><text 
class="breeze-ci-image-build-r6" x="158.6" y="1874.4" textLength="24.4" 
clip-path="url(#breeze-ci-image-build-line-76)">-a</text><text 
class="breeze-ci-image-build-r1" x="207.4" y="1874.4" textLength="3 [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="1898.8" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-77)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1898.8" textLength="109.8" 
clip-path="url(#breeze-ci-image-build-line-77)">--dry-run</text><text 
class="breeze-ci-image-build-r6" x="158.6" y="1898.8" textLength="24.4" 
clip-path="url(#breeze-ci-image-build-line-77)">-D</text><text 
class="breeze-ci-image-build-r1" x="207.4" y="1898.8" textLength="719.8" [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="1923.2" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-78)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1923.2" textLength="109.8" 
clip-path="url(#breeze-ci-image-build-line-78)">--verbose</text><text 
class="breeze-ci-image-build-r6" x="158.6" y="1923.2" textLength="24.4" 
clip-path="url(#breeze-ci-image-build-line-78)">-v</text><text 
class="breeze-ci-image-build-r1" x="207.4" y="1923.2" textLength="585.6" [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="1947.6" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-79)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1947.6" textLength="109.8" 
clip-path="url(#breeze-ci-image-build-line-79)">--help&#160;&#160;&#160;</text><text
 class="breeze-ci-image-build-r6" x="158.6" y="1947.6" textLength="24.4" 
clip-path="url(#breeze-ci-image-build-line-79)">-h</text><text 
class="breeze-ci-image-build-r1" x="207.4" y="1947.6" tex [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="1972" textLength="1464" 
clip-path="url(#breeze-ci-image-build-line-80)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-ci-image-build-r1" x="1464" y="1972" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-80)">
 </text>
     </g>
     </g>
diff --git a/dev/breeze/doc/images/output_ci-image_build.txt 
b/dev/breeze/doc/images/output_ci-image_build.txt
index a1bafba4172..f0c4db66c4a 100644
--- a/dev/breeze/doc/images/output_ci-image_build.txt
+++ b/dev/breeze/doc/images/output_ci-image_build.txt
@@ -1 +1 @@
-52f338c973e63396bbffd450b229d6c9
+12742b1b784b6e3a33582d4fe5f90d1f
diff --git a/dev/breeze/doc/images/output_ci-image_load.svg 
b/dev/breeze/doc/images/output_ci-image_load.svg
index d6c510fd5e2..5972fb23a4d 100644
--- a/dev/breeze/doc/images/output_ci-image_load.svg
+++ b/dev/breeze/doc/images/output_ci-image_load.svg
@@ -1,4 +1,4 @@
-<svg class="rich-terminal" viewBox="0 0 1482 806.4" 
xmlns="http://www.w3.org/2000/svg";>
+<svg class="rich-terminal" viewBox="0 0 1482 830.8" 
xmlns="http://www.w3.org/2000/svg";>
     <!-- Generated with Rich https://www.textualize.io -->
     <style>
 
@@ -43,7 +43,7 @@
 
     <defs>
     <clipPath id="breeze-ci-image-load-clip-terminal">
-      <rect x="0" y="0" width="1463.0" height="755.4" />
+      <rect x="0" y="0" width="1463.0" height="779.8" />
     </clipPath>
     <clipPath id="breeze-ci-image-load-line-0">
     <rect x="0" y="1.5" width="1464" height="24.65"/>
@@ -135,9 +135,12 @@
 <clipPath id="breeze-ci-image-load-line-29">
     <rect x="0" y="709.1" width="1464" height="24.65"/>
             </clipPath>
+<clipPath id="breeze-ci-image-load-line-30">
+    <rect x="0" y="733.5" width="1464" height="24.65"/>
+            </clipPath>
     </defs>
 
-    <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" 
x="1" y="1" width="1480" height="804.4" rx="8"/><text 
class="breeze-ci-image-load-title" fill="#c5c8c6" text-anchor="middle" x="740" 
y="27">Command:&#160;ci-image&#160;load</text>
+    <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" 
x="1" y="1" width="1480" height="828.8" rx="8"/><text 
class="breeze-ci-image-load-title" fill="#c5c8c6" text-anchor="middle" x="740" 
y="27">Command:&#160;ci-image&#160;load</text>
             <g transform="translate(26,22)">
             <circle cx="0" cy="0" r="7" fill="#ff5f57"/>
             <circle cx="22" cy="0" r="7" fill="#febc2e"/>
@@ -172,12 +175,13 @@
 </text><text class="breeze-ci-image-load-r5" x="0" y="556.8" textLength="12.2" 
clip-path="url(#breeze-ci-image-load-line-22)">│</text><text 
class="breeze-ci-image-load-r1" x="414.8" y="556.8" textLength="256.2" 
clip-path="url(#breeze-ci-image-load-line-22)">from&#160;the&#160;latest&#160;job.&#160;</text><text
 class="breeze-ci-image-load-r7" x="671" y="556.8" textLength="73.2" 
clip-path="url(#breeze-ci-image-load-line-22)">(TEXT)</text><text 
class="breeze-ci-image-load-r5" x="1451.8" y=" [...]
 </text><text class="breeze-ci-image-load-r5" x="0" y="581.2" textLength="12.2" 
clip-path="url(#breeze-ci-image-load-line-23)">│</text><text 
class="breeze-ci-image-load-r4" x="24.4" y="581.2" textLength="317.2" 
clip-path="url(#breeze-ci-image-load-line-23)">--from-run&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-ci-image-load-r1" x="414.8" y="581.2" textLength="793" 
clip-path="url(#breeze-ci-image-load-line-23)"> [...]
 </text><text class="breeze-ci-image-load-r5" x="0" y="605.6" textLength="12.2" 
clip-path="url(#breeze-ci-image-load-line-24)">│</text><text 
class="breeze-ci-image-load-r4" x="24.4" y="605.6" textLength="317.2" 
clip-path="url(#breeze-ci-image-load-line-24)">--skip-image-file-deletion</text><text
 class="breeze-ci-image-load-r1" x="414.8" y="605.6" textLength="414.8" 
clip-path="url(#breeze-ci-image-load-line-24)">Skip&#160;image&#160;deletion&#160;after&#160;loading.</text><text
 class="bree [...]
-</text><text class="breeze-ci-image-load-r5" x="0" y="630" textLength="1464" 
clip-path="url(#breeze-ci-image-load-line-25)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-ci-image-load-r1" x="1464" y="630" textLength="12.2" 
clip-path="url(#breeze-ci-image-load-line-25)">
-</text><text class="breeze-ci-image-load-r5" x="0" y="654.4" textLength="24.4" 
clip-path="url(#breeze-ci-image-load-line-26)">╭─</text><text 
class="breeze-ci-image-load-r5" x="24.4" y="654.4" textLength="195.2" 
clip-path="url(#breeze-ci-image-load-line-26)">&#160;Common&#160;options&#160;</text><text
 class="breeze-ci-image-load-r5" x="219.6" y="654.4" textLength="1220" 
clip-path="url(#breeze-ci-image-load-line-26)">─────────────────────────────────────────────────────────────────────────
 [...]
-</text><text class="breeze-ci-image-load-r5" x="0" y="678.8" textLength="12.2" 
clip-path="url(#breeze-ci-image-load-line-27)">│</text><text 
class="breeze-ci-image-load-r4" x="24.4" y="678.8" textLength="109.8" 
clip-path="url(#breeze-ci-image-load-line-27)">--dry-run</text><text 
class="breeze-ci-image-load-r6" x="158.6" y="678.8" textLength="24.4" 
clip-path="url(#breeze-ci-image-load-line-27)">-D</text><text 
class="breeze-ci-image-load-r1" x="207.4" y="678.8" textLength="719.8" 
clip-path= [...]
-</text><text class="breeze-ci-image-load-r5" x="0" y="703.2" textLength="12.2" 
clip-path="url(#breeze-ci-image-load-line-28)">│</text><text 
class="breeze-ci-image-load-r4" x="24.4" y="703.2" textLength="109.8" 
clip-path="url(#breeze-ci-image-load-line-28)">--verbose</text><text 
class="breeze-ci-image-load-r6" x="158.6" y="703.2" textLength="24.4" 
clip-path="url(#breeze-ci-image-load-line-28)">-v</text><text 
class="breeze-ci-image-load-r1" x="207.4" y="703.2" textLength="585.6" 
clip-path= [...]
-</text><text class="breeze-ci-image-load-r5" x="0" y="727.6" textLength="12.2" 
clip-path="url(#breeze-ci-image-load-line-29)">│</text><text 
class="breeze-ci-image-load-r4" x="24.4" y="727.6" textLength="109.8" 
clip-path="url(#breeze-ci-image-load-line-29)">--help&#160;&#160;&#160;</text><text
 class="breeze-ci-image-load-r6" x="158.6" y="727.6" textLength="24.4" 
clip-path="url(#breeze-ci-image-load-line-29)">-h</text><text 
class="breeze-ci-image-load-r1" x="207.4" y="727.6" textLength="32 [...]
-</text><text class="breeze-ci-image-load-r5" x="0" y="752" textLength="1464" 
clip-path="url(#breeze-ci-image-load-line-30)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-ci-image-load-r1" x="1464" y="752" textLength="12.2" 
clip-path="url(#breeze-ci-image-load-line-30)">
+</text><text class="breeze-ci-image-load-r5" x="0" y="630" textLength="12.2" 
clip-path="url(#breeze-ci-image-load-line-25)">│</text><text 
class="breeze-ci-image-load-r4" x="24.4" y="630" textLength="317.2" 
clip-path="url(#breeze-ci-image-load-line-25)">--tag-as&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-ci-image-load-r1" x="414.8" y="630" textLength="671" 
clip-path="url(#breeze-ci-image-load-line-2 [...]
+</text><text class="breeze-ci-image-load-r5" x="0" y="654.4" textLength="1464" 
clip-path="url(#breeze-ci-image-load-line-26)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-ci-image-load-r1" x="1464" y="654.4" textLength="12.2" 
clip-path="url(#breeze-ci-image-load-line-26)">
+</text><text class="breeze-ci-image-load-r5" x="0" y="678.8" textLength="24.4" 
clip-path="url(#breeze-ci-image-load-line-27)">╭─</text><text 
class="breeze-ci-image-load-r5" x="24.4" y="678.8" textLength="195.2" 
clip-path="url(#breeze-ci-image-load-line-27)">&#160;Common&#160;options&#160;</text><text
 class="breeze-ci-image-load-r5" x="219.6" y="678.8" textLength="1220" 
clip-path="url(#breeze-ci-image-load-line-27)">─────────────────────────────────────────────────────────────────────────
 [...]
+</text><text class="breeze-ci-image-load-r5" x="0" y="703.2" textLength="12.2" 
clip-path="url(#breeze-ci-image-load-line-28)">│</text><text 
class="breeze-ci-image-load-r4" x="24.4" y="703.2" textLength="109.8" 
clip-path="url(#breeze-ci-image-load-line-28)">--dry-run</text><text 
class="breeze-ci-image-load-r6" x="158.6" y="703.2" textLength="24.4" 
clip-path="url(#breeze-ci-image-load-line-28)">-D</text><text 
class="breeze-ci-image-load-r1" x="207.4" y="703.2" textLength="719.8" 
clip-path= [...]
+</text><text class="breeze-ci-image-load-r5" x="0" y="727.6" textLength="12.2" 
clip-path="url(#breeze-ci-image-load-line-29)">│</text><text 
class="breeze-ci-image-load-r4" x="24.4" y="727.6" textLength="109.8" 
clip-path="url(#breeze-ci-image-load-line-29)">--verbose</text><text 
class="breeze-ci-image-load-r6" x="158.6" y="727.6" textLength="24.4" 
clip-path="url(#breeze-ci-image-load-line-29)">-v</text><text 
class="breeze-ci-image-load-r1" x="207.4" y="727.6" textLength="585.6" 
clip-path= [...]
+</text><text class="breeze-ci-image-load-r5" x="0" y="752" textLength="12.2" 
clip-path="url(#breeze-ci-image-load-line-30)">│</text><text 
class="breeze-ci-image-load-r4" x="24.4" y="752" textLength="109.8" 
clip-path="url(#breeze-ci-image-load-line-30)">--help&#160;&#160;&#160;</text><text
 class="breeze-ci-image-load-r6" x="158.6" y="752" textLength="24.4" 
clip-path="url(#breeze-ci-image-load-line-30)">-h</text><text 
class="breeze-ci-image-load-r1" x="207.4" y="752" textLength="329.4" cli [...]
+</text><text class="breeze-ci-image-load-r5" x="0" y="776.4" textLength="1464" 
clip-path="url(#breeze-ci-image-load-line-31)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-ci-image-load-r1" x="1464" y="776.4" textLength="12.2" 
clip-path="url(#breeze-ci-image-load-line-31)">
 </text>
     </g>
     </g>
diff --git a/dev/breeze/doc/images/output_ci-image_load.txt 
b/dev/breeze/doc/images/output_ci-image_load.txt
index 2f2f4d04205..8a10a0f9d11 100644
--- a/dev/breeze/doc/images/output_ci-image_load.txt
+++ b/dev/breeze/doc/images/output_ci-image_load.txt
@@ -1 +1 @@
-baf0bfe0e270b6982bbfbfd97707a1ba
+9585cbea5c5cf55ef6b49273f4523fed
diff --git a/dev/breeze/src/airflow_breeze/commands/ci_image_commands.py 
b/dev/breeze/src/airflow_breeze/commands/ci_image_commands.py
index e9b34bfd164..ad1c38e30c4 100644
--- a/dev/breeze/src/airflow_breeze/commands/ci_image_commands.py
+++ b/dev/breeze/src/airflow_breeze/commands/ci_image_commands.py
@@ -36,6 +36,7 @@ from airflow_breeze.commands.common_image_options import (
     option_additional_python_deps,
     option_airflow_constraints_reference_build,
     option_build_progress,
+    option_cache_from_image,
     option_debian_version,
     option_dev_apt_command,
     option_dev_apt_deps,
@@ -52,6 +53,7 @@ from airflow_breeze.commands.common_image_options import (
     option_push,
     option_python_image,
     option_skip_image_file_deletion,
+    option_tag_as,
     option_verify,
     option_wait_for_image,
 )
@@ -247,6 +249,7 @@ option_ci_image_file_to_load = click.option(
 @option_answer
 @option_build_progress
 @option_builder
+@option_cache_from_image
 @option_commit_sha
 @option_debian_version
 @option_debug_resources
@@ -286,6 +289,7 @@ def build(
     airflow_constraints_reference: str,
     build_progress: str,
     builder: str,
+    cache_from_image: str | None,
     commit_sha: str | None,
     debian_version: str,
     debug_resources: bool,
@@ -338,6 +342,7 @@ def build(
         airflow_constraints_reference=airflow_constraints_reference,
         build_progress=build_progress,
         builder=builder,
+        cache_from_image=cache_from_image,
         commit_sha=commit_sha,
         debian_version=debian_version,
         dev_apt_command=dev_apt_command,
@@ -558,6 +563,7 @@ def save(
 @option_platform_single
 @option_python
 @option_skip_image_file_deletion
+@option_tag_as
 @option_verbose
 def load(
     from_run: str | None,
@@ -569,6 +575,7 @@ def load(
     platform: str,
     python: str,
     skip_image_file_deletion: bool,
+    tag_as: str | None,
 ):
     """Load CI image from a file."""
     perform_environment_checks()
@@ -618,6 +625,15 @@ def load(
     if result.returncode != 0:
         console_print(f"[error]Error when loading image: {result.stdout}[/]")
         sys.exit(result.returncode)
+    if tag_as:
+        console_print(f"[info]Tagging {build_ci_params.airflow_image_name} as 
{tag_as}[/]")
+        result = run_command(
+            ["docker", "tag", build_ci_params.airflow_image_name, tag_as],
+            check=False,
+        )
+        if result.returncode != 0:
+            console_print(f"[error]Error when tagging image: 
{result.stdout}[/]")
+            sys.exit(result.returncode)
     if not skip_image_file_deletion:
         console_print(f"[info]Deleting image file {image_file_to_load}[/]")
         image_file_to_load.unlink()
diff --git a/dev/breeze/src/airflow_breeze/commands/ci_image_commands_config.py 
b/dev/breeze/src/airflow_breeze/commands/ci_image_commands_config.py
index b9e0cf161a9..dd60ef4f2bf 100644
--- a/dev/breeze/src/airflow_breeze/commands/ci_image_commands_config.py
+++ b/dev/breeze/src/airflow_breeze/commands/ci_image_commands_config.py
@@ -57,6 +57,7 @@ CI_IMAGE_TOOLS_PARAMETERS: dict[str, list[dict[str, str | 
list[str]]]] = {
             "name": "Advanced build options (for power users)",
             "options": [
                 "--additional-pip-install-flags",
+                "--cache-from-image",
                 "--commit-sha",
                 "--debian-version",
                 "--disable-airflow-repo-cache",
@@ -183,6 +184,7 @@ CI_IMAGE_TOOLS_PARAMETERS: dict[str, list[dict[str, str | 
list[str]]]] = {
                 "--from-pr",
                 "--from-run",
                 "--skip-image-file-deletion",
+                "--tag-as",
             ],
         },
     ],
diff --git a/dev/breeze/src/airflow_breeze/commands/common_image_options.py 
b/dev/breeze/src/airflow_breeze/commands/common_image_options.py
index 5c545fbfbeb..696b1d93146 100644
--- a/dev/breeze/src/airflow_breeze/commands/common_image_options.py
+++ b/dev/breeze/src/airflow_breeze/commands/common_image_options.py
@@ -101,6 +101,12 @@ option_build_progress = click.option(
     show_default=True,
     default=ALLOWED_BUILD_PROGRESS[0],
 )
+option_cache_from_image = click.option(
+    "--cache-from-image",
+    help="Additional image to read the build cache from - it has to be 
pullable from a registry, "
+    "an image only present in the local docker engine contributes no cache.",
+    envvar="CACHE_FROM_IMAGE",
+)
 option_debian_version = click.option(
     "--debian-version",
     type=BetterChoice(ALLOWED_DEBIAN_VERSIONS),
@@ -200,6 +206,11 @@ option_skip_image_file_deletion = click.option(
     is_flag=True,
     envvar="SKIP_IMAGE_FILE_DELETION",
 )
+option_tag_as = click.option(
+    "--tag-as",
+    help="Additionally tag the loaded image with this reference.",
+    envvar="TAG_AS",
+)
 option_from_run = click.option(
     "--from-run",
     required=False,
diff --git a/dev/breeze/src/airflow_breeze/params/common_build_params.py 
b/dev/breeze/src/airflow_breeze/params/common_build_params.py
index 60f6034f925..01fc0d54061 100644
--- a/dev/breeze/src/airflow_breeze/params/common_build_params.py
+++ b/dev/breeze/src/airflow_breeze/params/common_build_params.py
@@ -51,6 +51,7 @@ class CommonBuildParams:
     airflow_constraints_location: str | None = None
     builder: str = "autodetect"
     build_progress: str = ALLOWED_BUILD_PROGRESS[0]
+    cache_from_image: str | None = None
     constraints_github_repository: str = APACHE_AIRFLOW_GITHUB_REPOSITORY
     commit_sha: str | None = None
     dev_apt_command: str | None = None
@@ -107,6 +108,15 @@ class CommonBuildParams:
             extra_flags.append(f"--cache-from={self.get_cache(self.platform)}")
         elif self.docker_cache == "disabled":
             extra_flags.append("--no-cache")
+        if self.cache_from_image:
+            extra_flags.append(f"--cache-from={self.cache_from_image}")
+        if self.docker_cache != "disabled":
+            # Records the layer cache keys in the image config, which is what 
lets an image
+            # built by an earlier run serve as `--cache-from` for a later one. 
BuildKit only
+            # honours those records when it can pull the layers from a 
registry, so an image
+            # that travelled as a saved tarball has to be pushed to one (even 
a throw-away
+            # local registry) before it can be passed as `--cache-from-image`.
+            extra_flags.append("--build-arg=BUILDKIT_INLINE_CACHE=1")
         return extra_flags
 
     @property
diff --git a/dev/breeze/tests/test_build_ci_params.py 
b/dev/breeze/tests/test_build_ci_params.py
new file mode 100644
index 00000000000..f652ca2f613
--- /dev/null
+++ b/dev/breeze/tests/test_build_ci_params.py
@@ -0,0 +1,57 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+from __future__ import annotations
+
+import pytest
+
+from airflow_breeze.params.build_ci_params import BuildCiParams
+
+INLINE_CACHE_FLAG = "--build-arg=BUILDKIT_INLINE_CACHE=1"
+
+
[email protected](
+    ("docker_cache", "inline_cache_expected"),
+    [
+        pytest.param("registry", True, id="registry"),
+        pytest.param("local", True, id="local"),
+        pytest.param("disabled", False, id="disabled"),
+    ],
+)
+def test_inline_cache_recorded_unless_cache_disabled(docker_cache: str, 
inline_cache_expected: bool):
+    flags = BuildCiParams(docker_cache=docker_cache).common_docker_build_flags
+    assert (INLINE_CACHE_FLAG in flags) is inline_cache_expected
+
+
+def test_cache_from_image_added_next_to_registry_cache():
+    params = BuildCiParams(docker_cache="registry", 
cache_from_image="localhost:5000/ci-image-cache:3.12")
+    flags = params.common_docker_build_flags
+    assert f"--cache-from={params.get_cache(params.platform)}" in flags
+    assert "--cache-from=localhost:5000/ci-image-cache:3.12" in flags
+
+
+def test_cache_from_image_added_when_registry_cache_is_not_used():
+    flags = BuildCiParams(
+        docker_cache="disabled", 
cache_from_image="localhost:5000/ci-image-cache:3.12"
+    ).common_docker_build_flags
+    assert "--cache-from=localhost:5000/ci-image-cache:3.12" in flags
+    assert "--no-cache" in flags
+
+
+def test_no_cache_from_image_flag_when_unset():
+    flags = BuildCiParams(docker_cache="registry").common_docker_build_flags
+    assert not any(flag.startswith("--cache-from=localhost") for flag in flags)

Reply via email to