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 2d9bb46cd6 Use `uv` as packaging tool used in CI builds (#37692)
2d9bb46cd6 is described below

commit 2d9bb46cd60583c56603141fe555e7e065950a6a
Author: Jarek Potiuk <[email protected]>
AuthorDate: Mon Feb 26 13:10:31 2024 +0100

    Use `uv` as packaging tool used in CI builds (#37692)
    
    The `uv` tool released in Feb 2024 by ruff creators provides a
    way faster drop-in replacement to `pip` and we are using it now
    in our CI, when it can bring significant speed improvements (and
    soon possibly more features).
    
    This PR replaces `pip install` and `pip uninstall` with equivalent
    `uv pip install` and `uv pip uninstall` commands, controlled by
    a single `AIRFLOW_USE_UV` ARG. In CI images it is set to "true" so
    CI images are prepared using UV, but PROD images (which are also
    used during CI tests) are built using pip. This way we can get
    both - stability and compliance for user-facing `pip` installation
    and speed and new features coming from `uv`.
---
 .github/workflows/ci.yml                           |   2 +-
 Dockerfile                                         | 169 ++++++++++++++------
 Dockerfile.ci                                      | 175 +++++++++++++++------
 dev/breeze/doc/ci/02_images.md                     |   3 +
 dev/breeze/doc/images/output_ci-image_build.svg    |  96 +++++------
 dev/breeze/doc/images/output_ci-image_build.txt    |   2 +-
 dev/breeze/doc/images/output_prod-image_build.svg  |  18 +--
 dev/breeze/doc/images/output_prod-image_build.txt  |   2 +-
 .../airflow_breeze/commands/ci_image_commands.py   |  11 ++
 .../commands/ci_image_commands_config.py           |   1 +
 .../commands/production_image_commands_config.py   |   4 +-
 .../commands/release_management_commands.py        |   2 +
 .../src/airflow_breeze/params/build_ci_params.py   |   2 +
 .../src/airflow_breeze/params/build_prod_params.py |   2 +-
 docs/docker-stack/build-arg-ref.rst                |   4 +
 scripts/docker/common.sh                           |  71 ++++++++-
 scripts/docker/entrypoint_ci.sh                    |  32 ++--
 scripts/docker/install_additional_dependencies.sh  |  12 +-
 scripts/docker/install_airflow.sh                  |  22 ++-
 ...install_airflow_dependencies_from_branch_tip.sh |  12 +-
 .../docker/install_from_docker_context_files.sh    |  23 ++-
 scripts/docker/install_mssql.sh                    |   1 -
 scripts/docker/install_pip_version.sh              |   7 +-
 scripts/docker/install_pipx_tools.sh               |   3 +-
 scripts/in_container/_in_container_utils.sh        |  37 +++++
 tests/cli/commands/test_webserver_command.py       |   7 +-
 tests/providers/weaviate/hooks/test_weaviate.py    |   9 +-
 .../providers/weaviate/operators/test_weaviate.py  |   4 +-
 28 files changed, 506 insertions(+), 227 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index bc730277ce..409dfaf44d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -175,7 +175,7 @@ jobs:
   # We only push CI cache as PROD cache usually does not gain as much from 
fresh cache because
   # it uses prepared airflow and provider packages that invalidate the cache 
anyway most of the time
   push-early-buildx-cache-to-github-registry:
-    timeout-minutes: 50
+    timeout-minutes: 110
     name: "Push Early Image Cache"
     runs-on: ${{fromJSON(needs.build-info.outputs.runs-on)}}
     needs:
diff --git a/Dockerfile b/Dockerfile
index 4ac935925f..f5efda7abe 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -49,6 +49,8 @@ ARG AIRFLOW_VERSION="2.8.1"
 ARG PYTHON_BASE_IMAGE="python:3.8-slim-bookworm"
 
 ARG AIRFLOW_PIP_VERSION=24.0
+ARG AIRFLOW_UV_VERSION=0.1.10
+ARG AIRFLOW_USE_UV="false"
 ARG AIRFLOW_IMAGE_REPOSITORY="https://github.com/apache/airflow";
 ARG 
AIRFLOW_IMAGE_README_URL="https://raw.githubusercontent.com/apache/airflow/main/docs/docker-stack/README.md";
 
@@ -336,7 +338,6 @@ set -euo pipefail
 common::get_colors
 declare -a packages
 
-: "${AIRFLOW_PIP_VERSION:?Should be set}"
 : "${INSTALL_MSSQL_CLIENT:?Should be true or false}"
 
 
@@ -416,14 +417,13 @@ COPY <<"EOF" /install_pip_version.sh
 #!/usr/bin/env bash
 . "$( dirname "${BASH_SOURCE[0]}" )/common.sh"
 
-: "${AIRFLOW_PIP_VERSION:?Should be set}"
-
 common::get_colors
+common::get_packaging_tool
 common::get_airflow_version_specification
 common::override_pip_version_if_needed
-common::show_pip_version_and_location
+common::show_packaging_tool_version_and_location
 
-common::install_pip_version
+common::install_packaging_tool
 EOF
 
 # The content below is automatically copied from 
scripts/docker/install_airflow_dependencies_from_branch_tip.sh
@@ -436,7 +436,6 @@ COPY <<"EOF" 
/install_airflow_dependencies_from_branch_tip.sh
 : "${AIRFLOW_BRANCH:?Should be set}"
 : "${INSTALL_MYSQL_CLIENT:?Should be true or false}"
 : "${INSTALL_POSTGRES_CLIENT:?Should be true or false}"
-: "${AIRFLOW_PIP_VERSION:?Should be set}"
 
 function install_airflow_dependencies_from_branch_tip() {
     echo
@@ -452,26 +451,27 @@ function install_airflow_dependencies_from_branch_tip() {
     # dependencies that we can cache and reuse when installing airflow using 
constraints and latest
     # pyproject.toml in the next step (when we install regular airflow).
     set -x
-    pip install --root-user-action ignore \
+    ${PACKAGING_TOOL_CMD} install ${EXTRA_INSTALL_FLAGS} \
       ${ADDITIONAL_PIP_INSTALL_FLAGS} \
       "apache-airflow[${AIRFLOW_EXTRAS}] @ 
https://github.com/${AIRFLOW_REPO}/archive/${AIRFLOW_BRANCH}.tar.gz";
-    common::install_pip_version
+    common::install_packaging_tool
     # Uninstall airflow and providers to keep only the dependencies. In the 
future when
     # planned https://github.com/pypa/pip/issues/11440 is implemented in pip 
we might be able to use this
     # flag and skip the remove step.
-    pip freeze | grep apache-airflow-providers | xargs pip uninstall --yes 
2>/dev/null || true
+    ${PACKAGING_TOOL_CMD} freeze | grep apache-airflow-providers | xargs 
${PACKAGING_TOOL_CMD} uninstall ${EXTRA_UNINSTALL_FLAGS} 2>/dev/null || true
     set +x
     echo
     echo "${COLOR_BLUE}Uninstalling just airflow. Dependencies remain. Now 
target airflow can be reinstalled using mostly cached 
dependencies${COLOR_RESET}"
     echo
-    pip uninstall --yes apache-airflow || true
+    ${PACKAGING_TOOL_CMD} uninstall ${EXTRA_UNINSTALL_FLAGS} apache-airflow || 
true
 }
 
 common::get_colors
+common::get_packaging_tool
 common::get_airflow_version_specification
 common::override_pip_version_if_needed
 common::get_constraints_location
-common::show_pip_version_and_location
+common::show_packaging_tool_version_and_location
 
 install_airflow_dependencies_from_branch_tip
 EOF
@@ -481,6 +481,10 @@ COPY <<"EOF" /common.sh
 #!/usr/bin/env bash
 set -euo pipefail
 
+: "${AIRFLOW_PIP_VERSION:?Should be set}"
+: "${AIRFLOW_UV_VERSION:?Should be set}"
+: "${AIRFLOW_USE_UV:?Should be set}"
+
 function common::get_colors() {
     COLOR_BLUE=$'\e[34m'
     COLOR_GREEN=$'\e[32m'
@@ -494,6 +498,40 @@ function common::get_colors() {
     export COLOR_YELLOW
 }
 
+function common::get_packaging_tool() {
+    ## IMPORTANT: IF YOU MODIFY THIS FUNCTION YOU SHOULD ALSO MODIFY 
CORRESPONDING FUNCTION IN
+    ## `scripts/in_container/_in_container_utils.sh`
+    if [[ ${AIRFLOW_USE_UV} == "true" ]]; then
+        echo
+        echo "${COLOR_BLUE}Using 'uv' to install Airflow${COLOR_RESET}"
+        echo
+        export PACKAGING_TOOL="uv"
+        export PACKAGING_TOOL_CMD="uv pip"
+        export EXTRA_INSTALL_FLAGS=""
+        export EXTRA_UNINSTALL_FLAGS=""
+        export RESOLUTION_HIGHEST_FLAG="--resolution highest"
+        export RESOLUTION_LOWEST_DIRECT_FLAG="--resolution lowest-direct"
+        # We need to lie about VIRTUAL_ENV to make uv works
+        # Until https://github.com/astral-sh/uv/issues/1396 is fixed
+        # In case we are running user installation, we need to set VIRTUAL_ENV 
to user's home + .local
+        if [[ ${PIP_USER=} == "true" ]]; then
+            VIRTUAL_ENV="${HOME}/.local"
+        else
+            VIRTUAL_ENV=$(python -c "import sys; print(sys.prefix)")
+        fi
+        export VIRTUAL_ENV
+    else
+        echo
+        echo "${COLOR_BLUE}Using 'pip' to install Airflow${COLOR_RESET}"
+        echo
+        export PACKAGING_TOOL="pip"
+        export PACKAGING_TOOL_CMD="pip"
+        export EXTRA_INSTALL_FLAGS="--root-user-action ignore"
+        export EXTRA_UNINSTALL_FLAGS="--yes"
+        export RESOLUTION_HIGHEST_FLAG="--upgrade-strategy eager"
+        export RESOLUTION_LOWEST_DIRECT_FLAG="--upgrade --upgrade-strategy 
only-if-needed"
+    fi
+}
 
 function common::get_airflow_version_specification() {
     if [[ -z ${AIRFLOW_VERSION_SPECIFICATION=}
@@ -529,20 +567,41 @@ function common::get_constraints_location() {
     fi
 }
 
-function common::show_pip_version_and_location() {
+function common::show_packaging_tool_version_and_location() {
    echo "PATH=${PATH}"
-   echo "pip on path: $(which pip)"
-   echo "Using pip: $(pip --version)"
+   if [[ ${PACKAGING_TOOL} == "pip" ]]; then
+       echo "${COLOR_BLUE}Using 'pip' to install Airflow${COLOR_RESET}"
+       echo "pip on path: $(which pip)"
+       echo "Using pip: $(pip --version)"
+   else
+       echo "${COLOR_BLUE}Using 'uv' to install Airflow${COLOR_RESET}"
+       echo "uv on path: $(which uv)"
+       echo "Using uv: $(uv --version)"
+   fi
 }
 
-function common::install_pip_version() {
+function common::install_packaging_tool() {
     echo
     echo "${COLOR_BLUE}Installing pip version 
${AIRFLOW_PIP_VERSION}${COLOR_RESET}"
     echo
     if [[ ${AIRFLOW_PIP_VERSION} =~ .*https.* ]]; then
-        pip install --disable-pip-version-check "pip @ ${AIRFLOW_PIP_VERSION}"
+        # shellcheck disable=SC2086
+        pip install --root-user-action ignore --disable-pip-version-check "pip 
@ ${AIRFLOW_PIP_VERSION}"
     else
-        pip install --disable-pip-version-check "pip==${AIRFLOW_PIP_VERSION}"
+        # shellcheck disable=SC2086
+        pip install --root-user-action ignore --disable-pip-version-check 
"pip==${AIRFLOW_PIP_VERSION}"
+    fi
+    if [[ ${AIRFLOW_USE_UV} == "true" ]]; then
+        echo
+        echo "${COLOR_BLUE}Installing uv version 
${AIRFLOW_UV_VERSION}${COLOR_RESET}"
+        echo
+        if [[ ${AIRFLOW_UV_VERSION} =~ .*https.* ]]; then
+            # shellcheck disable=SC2086
+            pip install --root-user-action ignore --disable-pip-version-check 
"uv @ ${AIRFLOW_UV_VERSION}"
+        else
+            # shellcheck disable=SC2086
+            pip install --root-user-action ignore --disable-pip-version-check 
"uv==${AIRFLOW_UV_VERSION}"
+        fi
     fi
     mkdir -p "${HOME}/.local/bin"
 }
@@ -601,8 +660,6 @@ COPY <<"EOF" /install_from_docker_context_files.sh
 
 . "$( dirname "${BASH_SOURCE[0]}" )/common.sh"
 
-: "${AIRFLOW_PIP_VERSION:?Should be set}"
-
 function install_airflow_and_providers_from_docker_context_files(){
     if [[ ${INSTALL_MYSQL_CLIENT} != "true" ]]; then
         AIRFLOW_EXTRAS=${AIRFLOW_EXTRAS/mysql,}
@@ -619,7 +676,7 @@ function 
install_airflow_and_providers_from_docker_context_files(){
     fi
 
     # shellcheck disable=SC2206
-    local pip_flags=(
+    local packaging_flags=(
         # Don't quote this -- if it is empty we don't want it to create an
         # empty array element
         --find-links="file:///docker-context-files"
@@ -669,7 +726,7 @@ function 
install_airflow_and_providers_from_docker_context_files(){
             echo
             # force reinstall all airflow + provider packages with constraints 
found in
             set -x
-            pip install "${pip_flags[@]}" --root-user-action ignore --upgrade \
+            ${PACKAGING_TOOL_CMD} install ${EXTRA_INSTALL_FLAGS} 
"${packaging_flags[@]}" --upgrade \
                 ${ADDITIONAL_PIP_INSTALL_FLAGS} --constraint 
"${local_constraints_file}" \
                 ${reinstalling_apache_airflow_package} 
${reinstalling_apache_airflow_providers_packages}
             set +x
@@ -678,7 +735,7 @@ function 
install_airflow_and_providers_from_docker_context_files(){
             echo "${COLOR_BLUE}Installing docker-context-files packages with 
constraints from GitHub${COLOR_RESET}"
             echo
             set -x
-            pip install "${pip_flags[@]}" --root-user-action ignore \
+            ${PACKAGING_TOOL_CMD} install ${EXTRA_INSTALL_FLAGS} 
"${packaging_flags[@]}" \
                 ${ADDITIONAL_PIP_INSTALL_FLAGS} \
                 --constraint "${AIRFLOW_CONSTRAINTS_LOCATION}" \
                 ${reinstalling_apache_airflow_package} 
${reinstalling_apache_airflow_providers_packages}
@@ -689,17 +746,16 @@ function 
install_airflow_and_providers_from_docker_context_files(){
         echo "${COLOR_BLUE}Installing docker-context-files packages without 
constraints${COLOR_RESET}"
         echo
         set -x
-        pip install "${pip_flags[@]}" --root-user-action ignore \
+        ${PACKAGING_TOOL_CMD} install ${EXTRA_INSTALL_FLAGS} 
"${packaging_flags[@]}" \
             ${ADDITIONAL_PIP_INSTALL_FLAGS} \
             ${reinstalling_apache_airflow_package} 
${reinstalling_apache_airflow_providers_packages}
         set +x
     fi
-    common::install_pip_version
+    common::install_packaging_tool
     pip check
 }
 
 function install_all_other_packages_from_docker_context_files() {
-
     echo
     echo "${COLOR_BLUE}Force re-installing all other package from local files 
without dependencies${COLOR_RESET}"
     echo
@@ -709,22 +765,22 @@ function 
install_all_other_packages_from_docker_context_files() {
         grep -v apache_airflow | grep -v apache-airflow || true)
     if [[ -n "${reinstalling_other_packages}" ]]; then
         set -x
-        pip install ${ADDITIONAL_PIP_INSTALL_FLAGS} \
-            --root-user-action ignore --force-reinstall --no-deps --no-index 
${reinstalling_other_packages}
-        common::install_pip_version
+        ${PACKAGING_TOOL_CMD} install ${EXTRA_INSTALL_FLAGS} 
${ADDITIONAL_PIP_INSTALL_FLAGS} \
+            --force-reinstall --no-deps --no-index 
${reinstalling_other_packages}
+        common::install_packaging_tool
         set +x
     fi
 }
 
 common::get_colors
+common::get_packaging_tool
 common::get_airflow_version_specification
 common::override_pip_version_if_needed
 common::get_constraints_location
-common::show_pip_version_and_location
+common::show_packaging_tool_version_and_location
 
 install_airflow_and_providers_from_docker_context_files
 
-common::show_pip_version_and_location
 install_all_other_packages_from_docker_context_files
 EOF
 
@@ -734,8 +790,6 @@ COPY <<"EOF" /install_airflow.sh
 
 . "$( dirname "${BASH_SOURCE[0]}" )/common.sh"
 
-: "${AIRFLOW_PIP_VERSION:?Should be set}"
-
 function install_airflow() {
     # Coherence check for editable installation mode.
     if [[ ${AIRFLOW_INSTALLATION_METHOD} != "." && \
@@ -760,22 +814,21 @@ function install_airflow() {
         echo "${COLOR_BLUE}Remove airflow and all provider packages installed 
before potentially${COLOR_RESET}"
         echo
         set -x
-        pip freeze | grep apache-airflow | xargs pip uninstall --yes 
2>/dev/null || true
+        ${PACKAGING_TOOL_CMD} freeze | grep apache-airflow | xargs 
${PACKAGING_TOOL_CMD} uninstall ${EXTRA_UNINSTALL_FLAGS} 2>/dev/null || true
         set +x
         echo
         echo "${COLOR_BLUE}Installing all packages with eager upgrade with 
${AIRFLOW_INSTALL_EDITABLE_FLAG} mode${COLOR_RESET}"
         echo
         set -x
-        pip install --root-user-action ignore \
-            --upgrade --upgrade-strategy eager \
+        ${PACKAGING_TOOL_CMD} install ${EXTRA_INSTALL_FLAGS} --upgrade 
${RESOLUTION_HIGHEST_FLAG} \
             ${ADDITIONAL_PIP_INSTALL_FLAGS} \
             ${AIRFLOW_INSTALL_EDITABLE_FLAG} \
             
"${AIRFLOW_INSTALLATION_METHOD}[${AIRFLOW_EXTRAS}]${AIRFLOW_VERSION_SPECIFICATION}"
 \
             ${EAGER_UPGRADE_ADDITIONAL_REQUIREMENTS=}
         set +x
-        common::install_pip_version
+        common::install_packaging_tool
         echo
-        echo "${COLOR_BLUE}Running 'pip check'${COLOR_RESET}"
+        echo "${COLOR_BLUE}Running '${PACKAGING_TOOL} check'${COLOR_RESET}"
         echo
         pip check
     else
@@ -783,17 +836,17 @@ function install_airflow() {
         echo "${COLOR_BLUE}Installing all packages with constraints and 
upgrade if needed${COLOR_RESET}"
         echo
         set -x
-        pip install --root-user-action ignore ${AIRFLOW_INSTALL_EDITABLE_FLAG} 
\
+        ${PACKAGING_TOOL_CMD} install ${EXTRA_INSTALL_FLAGS} 
${AIRFLOW_INSTALL_EDITABLE_FLAG} \
             ${ADDITIONAL_PIP_INSTALL_FLAGS} \
             
"${AIRFLOW_INSTALLATION_METHOD}[${AIRFLOW_EXTRAS}]${AIRFLOW_VERSION_SPECIFICATION}"
 \
             --constraint "${AIRFLOW_CONSTRAINTS_LOCATION}" || true
-        common::install_pip_version
+        common::install_packaging_tool
         # then upgrade if needed without using constraints to account for new 
limits in pyproject.toml
-        pip install --root-user-action ignore --upgrade --upgrade-strategy 
only-if-needed \
+        ${PACKAGING_TOOL_CMD} install ${EXTRA_INSTALL_FLAGS} --upgrade 
${RESOLUTION_LOWEST_DIRECT_FLAG} \
             ${ADDITIONAL_PIP_INSTALL_FLAGS} \
             ${AIRFLOW_INSTALL_EDITABLE_FLAG} \
             
"${AIRFLOW_INSTALLATION_METHOD}[${AIRFLOW_EXTRAS}]${AIRFLOW_VERSION_SPECIFICATION}"
-        common::install_pip_version
+        common::install_packaging_tool
         set +x
         echo
         echo "${COLOR_BLUE}Running 'pip check'${COLOR_RESET}"
@@ -804,10 +857,11 @@ function install_airflow() {
 }
 
 common::get_colors
+common::get_packaging_tool
 common::get_airflow_version_specification
 common::override_pip_version_if_needed
 common::get_constraints_location
-common::show_pip_version_and_location
+common::show_packaging_tool_version_and_location
 
 install_airflow
 EOF
@@ -819,7 +873,6 @@ set -euo pipefail
 
 : "${UPGRADE_TO_NEWER_DEPENDENCIES:?Should be true or false}"
 : "${ADDITIONAL_PYTHON_DEPS:?Should be set}"
-: "${AIRFLOW_PIP_VERSION:?Should be set}"
 
 . "$( dirname "${BASH_SOURCE[0]}" )/common.sh"
 
@@ -829,10 +882,10 @@ function install_additional_dependencies() {
         echo "${COLOR_BLUE}Installing additional dependencies while upgrading 
to newer dependencies${COLOR_RESET}"
         echo
         set -x
-        pip install --root-user-action ignore --upgrade --upgrade-strategy 
eager \
+        ${PACKAGING_TOOL_CMD} install ${EXTRA_INSTALL_FLAGS} --upgrade 
${RESOLUTION_HIGHEST_FLAG} \
             ${ADDITIONAL_PIP_INSTALL_FLAGS} \
             ${ADDITIONAL_PYTHON_DEPS} ${EAGER_UPGRADE_ADDITIONAL_REQUIREMENTS=}
-        common::install_pip_version
+        common::install_packaging_tool
         set +x
         echo
         echo "${COLOR_BLUE}Running 'pip check'${COLOR_RESET}"
@@ -843,10 +896,10 @@ function install_additional_dependencies() {
         echo "${COLOR_BLUE}Installing additional dependencies upgrading only 
if needed${COLOR_RESET}"
         echo
         set -x
-        pip install --root-user-action ignore --upgrade --upgrade-strategy 
only-if-needed \
+        ${PACKAGING_TOOL_CMD} install ${EXTRA_INSTALL_FLAGS} --upgrade 
"${RESOLUTION_LOWEST_DIRECT_FLAG}" \
             ${ADDITIONAL_PIP_INSTALL_FLAGS} \
             ${ADDITIONAL_PYTHON_DEPS}
-        common::install_pip_version
+        common::install_packaging_tool
         set +x
         echo
         echo "${COLOR_BLUE}Running 'pip check'${COLOR_RESET}"
@@ -856,10 +909,11 @@ function install_additional_dependencies() {
 }
 
 common::get_colors
+common::get_packaging_tool
 common::get_airflow_version_specification
 common::override_pip_version_if_needed
 common::get_constraints_location
-common::show_pip_version_and_location
+common::show_packaging_tool_version_and_location
 
 install_additional_dependencies
 EOF
@@ -1246,6 +1300,8 @@ ARG INSTALL_MYSQL_CLIENT_TYPE="mariadb"
 ARG INSTALL_MSSQL_CLIENT="true"
 ARG INSTALL_POSTGRES_CLIENT="true"
 ARG AIRFLOW_PIP_VERSION
+ARG AIRFLOW_UV_VERSION
+ARG AIRFLOW_USE_UV
 
 ENV INSTALL_MYSQL_CLIENT=${INSTALL_MYSQL_CLIENT} \
     INSTALL_MYSQL_CLIENT_TYPE=${INSTALL_MYSQL_CLIENT_TYPE} \
@@ -1327,6 +1383,8 @@ RUN if [[ -f /docker-context-files/pip.conf ]]; then \
 ARG ADDITIONAL_PIP_INSTALL_FLAGS=""
 
 ENV AIRFLOW_PIP_VERSION=${AIRFLOW_PIP_VERSION} \
+    AIRFLOW_UV_VERSION=${AIRFLOW_UV_VERSION} \
+    AIRFLOW_USE_UV=${AIRFLOW_USE_UV} \
     AIRFLOW_PRE_CACHED_PIP_PACKAGES=${AIRFLOW_PRE_CACHED_PIP_PACKAGES} \
     AIRFLOW_VERSION=${AIRFLOW_VERSION} \
     AIRFLOW_INSTALLATION_METHOD=${AIRFLOW_INSTALLATION_METHOD} \
@@ -1342,7 +1400,6 @@ ENV AIRFLOW_PIP_VERSION=${AIRFLOW_PIP_VERSION} \
     AIRFLOW_CONSTRAINTS_LOCATION=${AIRFLOW_CONSTRAINTS_LOCATION} \
     DEFAULT_CONSTRAINTS_BRANCH=${DEFAULT_CONSTRAINTS_BRANCH} \
     PATH=${PATH}:${AIRFLOW_USER_HOME_DIR}/.local/bin \
-    AIRFLOW_PIP_VERSION=${AIRFLOW_PIP_VERSION} \
     PIP_PROGRESS_BAR=${PIP_PROGRESS_BAR} \
     ADDITIONAL_PIP_INSTALL_FLAGS=${ADDITIONAL_PIP_INSTALL_FLAGS} \
     AIRFLOW_USER_HOME_DIR=${AIRFLOW_USER_HOME_DIR} \
@@ -1458,12 +1515,16 @@ LABEL org.apache.airflow.distro="debian" \
 
 ARG PYTHON_BASE_IMAGE
 ARG AIRFLOW_PIP_VERSION
+ARG AIRFLOW_UV_VERSION
+ARG AIRFLOW_USE_UV
 
 ENV PYTHON_BASE_IMAGE=${PYTHON_BASE_IMAGE} \
     # Make sure noninteractive debian install is used and language variables 
set
     DEBIAN_FRONTEND=noninteractive LANGUAGE=C.UTF-8 LANG=C.UTF-8 
LC_ALL=C.UTF-8 \
     LC_CTYPE=C.UTF-8 LC_MESSAGES=C.UTF-8 LD_LIBRARY_PATH=/usr/local/lib \
-    AIRFLOW_PIP_VERSION=${AIRFLOW_PIP_VERSION}
+    AIRFLOW_PIP_VERSION=${AIRFLOW_PIP_VERSION} \
+    AIRFLOW_UV_VERSION=${AIRFLOW_UV_VERSION} \
+    AIRFLOW_USE_UV=${AIRFLOW_USE_UV}
 
 ARG RUNTIME_APT_DEPS=""
 ARG ADDITIONAL_RUNTIME_APT_DEPS=""
@@ -1503,10 +1564,14 @@ ENV PATH="${AIRFLOW_USER_HOME_DIR}/.local/bin:${PATH}" \
     AIRFLOW_USER_HOME_DIR=${AIRFLOW_USER_HOME_DIR} \
     AIRFLOW_HOME=${AIRFLOW_HOME}
 
-# THE 3 LINES ARE ONLY NEEDED IN ORDER TO MAKE PYMSSQL BUILD WORK WITH LATEST 
CYTHON
+# THE 7 LINES ARE ONLY NEEDED IN ORDER TO MAKE PYMSSQL BUILD WORK WITH LATEST 
CYTHON
 # AND SHOULD BE REMOVED WHEN WORKAROUND IN install_mssql.sh IS REMOVED
 ARG AIRFLOW_PIP_VERSION=24.0
-ENV AIRFLOW_PIP_VERSION=${AIRFLOW_PIP_VERSION}
+ARG AIRFLOW_UV_VERSION=0.1.10
+ARG AIRFLOW_USE_UV="false"
+ENV AIRFLOW_PIP_VERSION=${AIRFLOW_PIP_VERSION} \
+    AIRFLOW_UV_VERSION=${AIRFLOW_UV_VERSION} \
+    AIRFLOW_USE_UV=${AIRFLOW_USE_UV}
 COPY --from=scripts common.sh /scripts/docker/
 
 # Only copy mysql/mssql installation scripts for now - so that changing the 
other
diff --git a/Dockerfile.ci b/Dockerfile.ci
index f5044a2ef9..8e927094e8 100644
--- a/Dockerfile.ci
+++ b/Dockerfile.ci
@@ -296,7 +296,6 @@ set -euo pipefail
 common::get_colors
 declare -a packages
 
-: "${AIRFLOW_PIP_VERSION:?Should be set}"
 : "${INSTALL_MSSQL_CLIENT:?Should be true or false}"
 
 
@@ -376,14 +375,13 @@ COPY <<"EOF" /install_pip_version.sh
 #!/usr/bin/env bash
 . "$( dirname "${BASH_SOURCE[0]}" )/common.sh"
 
-: "${AIRFLOW_PIP_VERSION:?Should be set}"
-
 common::get_colors
+common::get_packaging_tool
 common::get_airflow_version_specification
 common::override_pip_version_if_needed
-common::show_pip_version_and_location
+common::show_packaging_tool_version_and_location
 
-common::install_pip_version
+common::install_packaging_tool
 EOF
 
 # The content below is automatically copied from 
scripts/docker/install_airflow_dependencies_from_branch_tip.sh
@@ -396,7 +394,6 @@ COPY <<"EOF" 
/install_airflow_dependencies_from_branch_tip.sh
 : "${AIRFLOW_BRANCH:?Should be set}"
 : "${INSTALL_MYSQL_CLIENT:?Should be true or false}"
 : "${INSTALL_POSTGRES_CLIENT:?Should be true or false}"
-: "${AIRFLOW_PIP_VERSION:?Should be set}"
 
 function install_airflow_dependencies_from_branch_tip() {
     echo
@@ -412,26 +409,27 @@ function install_airflow_dependencies_from_branch_tip() {
     # dependencies that we can cache and reuse when installing airflow using 
constraints and latest
     # pyproject.toml in the next step (when we install regular airflow).
     set -x
-    pip install --root-user-action ignore \
+    ${PACKAGING_TOOL_CMD} install ${EXTRA_INSTALL_FLAGS} \
       ${ADDITIONAL_PIP_INSTALL_FLAGS} \
       "apache-airflow[${AIRFLOW_EXTRAS}] @ 
https://github.com/${AIRFLOW_REPO}/archive/${AIRFLOW_BRANCH}.tar.gz";
-    common::install_pip_version
+    common::install_packaging_tool
     # Uninstall airflow and providers to keep only the dependencies. In the 
future when
     # planned https://github.com/pypa/pip/issues/11440 is implemented in pip 
we might be able to use this
     # flag and skip the remove step.
-    pip freeze | grep apache-airflow-providers | xargs pip uninstall --yes 
2>/dev/null || true
+    ${PACKAGING_TOOL_CMD} freeze | grep apache-airflow-providers | xargs 
${PACKAGING_TOOL_CMD} uninstall ${EXTRA_UNINSTALL_FLAGS} 2>/dev/null || true
     set +x
     echo
     echo "${COLOR_BLUE}Uninstalling just airflow. Dependencies remain. Now 
target airflow can be reinstalled using mostly cached 
dependencies${COLOR_RESET}"
     echo
-    pip uninstall --yes apache-airflow || true
+    ${PACKAGING_TOOL_CMD} uninstall ${EXTRA_UNINSTALL_FLAGS} apache-airflow || 
true
 }
 
 common::get_colors
+common::get_packaging_tool
 common::get_airflow_version_specification
 common::override_pip_version_if_needed
 common::get_constraints_location
-common::show_pip_version_and_location
+common::show_packaging_tool_version_and_location
 
 install_airflow_dependencies_from_branch_tip
 EOF
@@ -441,6 +439,10 @@ COPY <<"EOF" /common.sh
 #!/usr/bin/env bash
 set -euo pipefail
 
+: "${AIRFLOW_PIP_VERSION:?Should be set}"
+: "${AIRFLOW_UV_VERSION:?Should be set}"
+: "${AIRFLOW_USE_UV:?Should be set}"
+
 function common::get_colors() {
     COLOR_BLUE=$'\e[34m'
     COLOR_GREEN=$'\e[32m'
@@ -454,6 +456,40 @@ function common::get_colors() {
     export COLOR_YELLOW
 }
 
+function common::get_packaging_tool() {
+    ## IMPORTANT: IF YOU MODIFY THIS FUNCTION YOU SHOULD ALSO MODIFY 
CORRESPONDING FUNCTION IN
+    ## `scripts/in_container/_in_container_utils.sh`
+    if [[ ${AIRFLOW_USE_UV} == "true" ]]; then
+        echo
+        echo "${COLOR_BLUE}Using 'uv' to install Airflow${COLOR_RESET}"
+        echo
+        export PACKAGING_TOOL="uv"
+        export PACKAGING_TOOL_CMD="uv pip"
+        export EXTRA_INSTALL_FLAGS=""
+        export EXTRA_UNINSTALL_FLAGS=""
+        export RESOLUTION_HIGHEST_FLAG="--resolution highest"
+        export RESOLUTION_LOWEST_DIRECT_FLAG="--resolution lowest-direct"
+        # We need to lie about VIRTUAL_ENV to make uv works
+        # Until https://github.com/astral-sh/uv/issues/1396 is fixed
+        # In case we are running user installation, we need to set VIRTUAL_ENV 
to user's home + .local
+        if [[ ${PIP_USER=} == "true" ]]; then
+            VIRTUAL_ENV="${HOME}/.local"
+        else
+            VIRTUAL_ENV=$(python -c "import sys; print(sys.prefix)")
+        fi
+        export VIRTUAL_ENV
+    else
+        echo
+        echo "${COLOR_BLUE}Using 'pip' to install Airflow${COLOR_RESET}"
+        echo
+        export PACKAGING_TOOL="pip"
+        export PACKAGING_TOOL_CMD="pip"
+        export EXTRA_INSTALL_FLAGS="--root-user-action ignore"
+        export EXTRA_UNINSTALL_FLAGS="--yes"
+        export RESOLUTION_HIGHEST_FLAG="--upgrade-strategy eager"
+        export RESOLUTION_LOWEST_DIRECT_FLAG="--upgrade --upgrade-strategy 
only-if-needed"
+    fi
+}
 
 function common::get_airflow_version_specification() {
     if [[ -z ${AIRFLOW_VERSION_SPECIFICATION=}
@@ -489,20 +525,41 @@ function common::get_constraints_location() {
     fi
 }
 
-function common::show_pip_version_and_location() {
+function common::show_packaging_tool_version_and_location() {
    echo "PATH=${PATH}"
-   echo "pip on path: $(which pip)"
-   echo "Using pip: $(pip --version)"
+   if [[ ${PACKAGING_TOOL} == "pip" ]]; then
+       echo "${COLOR_BLUE}Using 'pip' to install Airflow${COLOR_RESET}"
+       echo "pip on path: $(which pip)"
+       echo "Using pip: $(pip --version)"
+   else
+       echo "${COLOR_BLUE}Using 'uv' to install Airflow${COLOR_RESET}"
+       echo "uv on path: $(which uv)"
+       echo "Using uv: $(uv --version)"
+   fi
 }
 
-function common::install_pip_version() {
+function common::install_packaging_tool() {
     echo
     echo "${COLOR_BLUE}Installing pip version 
${AIRFLOW_PIP_VERSION}${COLOR_RESET}"
     echo
     if [[ ${AIRFLOW_PIP_VERSION} =~ .*https.* ]]; then
-        pip install --disable-pip-version-check "pip @ ${AIRFLOW_PIP_VERSION}"
+        # shellcheck disable=SC2086
+        pip install --root-user-action ignore --disable-pip-version-check "pip 
@ ${AIRFLOW_PIP_VERSION}"
     else
-        pip install --disable-pip-version-check "pip==${AIRFLOW_PIP_VERSION}"
+        # shellcheck disable=SC2086
+        pip install --root-user-action ignore --disable-pip-version-check 
"pip==${AIRFLOW_PIP_VERSION}"
+    fi
+    if [[ ${AIRFLOW_USE_UV} == "true" ]]; then
+        echo
+        echo "${COLOR_BLUE}Installing uv version 
${AIRFLOW_UV_VERSION}${COLOR_RESET}"
+        echo
+        if [[ ${AIRFLOW_UV_VERSION} =~ .*https.* ]]; then
+            # shellcheck disable=SC2086
+            pip install --root-user-action ignore --disable-pip-version-check 
"uv @ ${AIRFLOW_UV_VERSION}"
+        else
+            # shellcheck disable=SC2086
+            pip install --root-user-action ignore --disable-pip-version-check 
"uv==${AIRFLOW_UV_VERSION}"
+        fi
     fi
     mkdir -p "${HOME}/.local/bin"
 }
@@ -548,7 +605,7 @@ function install_pipx_tools() {
     echo "${COLOR_BLUE}Installing pipx tools${COLOR_RESET}"
     echo
     # Make sure PIPX is installed in latest version
-    pip install --root-user-action ignore  --upgrade "pipx>=1.2.1"
+    ${PACKAGING_TOOL_CMD} install ${EXTRA_INSTALL_FLAGS} --upgrade 
"pipx>=1.2.1"
     if [[ $(uname -m) != "aarch64" ]]; then
         # Do not install mssql-cli for ARM
         # Install all the tools we need available in command line but without 
impacting the current environment
@@ -562,6 +619,7 @@ function install_pipx_tools() {
 }
 
 common::get_colors
+common::get_packaging_tool
 
 install_pipx_tools
 EOF
@@ -572,8 +630,6 @@ COPY <<"EOF" /install_airflow.sh
 
 . "$( dirname "${BASH_SOURCE[0]}" )/common.sh"
 
-: "${AIRFLOW_PIP_VERSION:?Should be set}"
-
 function install_airflow() {
     # Coherence check for editable installation mode.
     if [[ ${AIRFLOW_INSTALLATION_METHOD} != "." && \
@@ -598,22 +654,21 @@ function install_airflow() {
         echo "${COLOR_BLUE}Remove airflow and all provider packages installed 
before potentially${COLOR_RESET}"
         echo
         set -x
-        pip freeze | grep apache-airflow | xargs pip uninstall --yes 
2>/dev/null || true
+        ${PACKAGING_TOOL_CMD} freeze | grep apache-airflow | xargs 
${PACKAGING_TOOL_CMD} uninstall ${EXTRA_UNINSTALL_FLAGS} 2>/dev/null || true
         set +x
         echo
         echo "${COLOR_BLUE}Installing all packages with eager upgrade with 
${AIRFLOW_INSTALL_EDITABLE_FLAG} mode${COLOR_RESET}"
         echo
         set -x
-        pip install --root-user-action ignore \
-            --upgrade --upgrade-strategy eager \
+        ${PACKAGING_TOOL_CMD} install ${EXTRA_INSTALL_FLAGS} --upgrade 
${RESOLUTION_HIGHEST_FLAG} \
             ${ADDITIONAL_PIP_INSTALL_FLAGS} \
             ${AIRFLOW_INSTALL_EDITABLE_FLAG} \
             
"${AIRFLOW_INSTALLATION_METHOD}[${AIRFLOW_EXTRAS}]${AIRFLOW_VERSION_SPECIFICATION}"
 \
             ${EAGER_UPGRADE_ADDITIONAL_REQUIREMENTS=}
         set +x
-        common::install_pip_version
+        common::install_packaging_tool
         echo
-        echo "${COLOR_BLUE}Running 'pip check'${COLOR_RESET}"
+        echo "${COLOR_BLUE}Running '${PACKAGING_TOOL} check'${COLOR_RESET}"
         echo
         pip check
     else
@@ -621,17 +676,17 @@ function install_airflow() {
         echo "${COLOR_BLUE}Installing all packages with constraints and 
upgrade if needed${COLOR_RESET}"
         echo
         set -x
-        pip install --root-user-action ignore ${AIRFLOW_INSTALL_EDITABLE_FLAG} 
\
+        ${PACKAGING_TOOL_CMD} install ${EXTRA_INSTALL_FLAGS} 
${AIRFLOW_INSTALL_EDITABLE_FLAG} \
             ${ADDITIONAL_PIP_INSTALL_FLAGS} \
             
"${AIRFLOW_INSTALLATION_METHOD}[${AIRFLOW_EXTRAS}]${AIRFLOW_VERSION_SPECIFICATION}"
 \
             --constraint "${AIRFLOW_CONSTRAINTS_LOCATION}" || true
-        common::install_pip_version
+        common::install_packaging_tool
         # then upgrade if needed without using constraints to account for new 
limits in pyproject.toml
-        pip install --root-user-action ignore --upgrade --upgrade-strategy 
only-if-needed \
+        ${PACKAGING_TOOL_CMD} install ${EXTRA_INSTALL_FLAGS} --upgrade 
${RESOLUTION_LOWEST_DIRECT_FLAG} \
             ${ADDITIONAL_PIP_INSTALL_FLAGS} \
             ${AIRFLOW_INSTALL_EDITABLE_FLAG} \
             
"${AIRFLOW_INSTALLATION_METHOD}[${AIRFLOW_EXTRAS}]${AIRFLOW_VERSION_SPECIFICATION}"
-        common::install_pip_version
+        common::install_packaging_tool
         set +x
         echo
         echo "${COLOR_BLUE}Running 'pip check'${COLOR_RESET}"
@@ -642,10 +697,11 @@ function install_airflow() {
 }
 
 common::get_colors
+common::get_packaging_tool
 common::get_airflow_version_specification
 common::override_pip_version_if_needed
 common::get_constraints_location
-common::show_pip_version_and_location
+common::show_packaging_tool_version_and_location
 
 install_airflow
 EOF
@@ -657,7 +713,6 @@ set -euo pipefail
 
 : "${UPGRADE_TO_NEWER_DEPENDENCIES:?Should be true or false}"
 : "${ADDITIONAL_PYTHON_DEPS:?Should be set}"
-: "${AIRFLOW_PIP_VERSION:?Should be set}"
 
 . "$( dirname "${BASH_SOURCE[0]}" )/common.sh"
 
@@ -667,10 +722,10 @@ function install_additional_dependencies() {
         echo "${COLOR_BLUE}Installing additional dependencies while upgrading 
to newer dependencies${COLOR_RESET}"
         echo
         set -x
-        pip install --root-user-action ignore --upgrade --upgrade-strategy 
eager \
+        ${PACKAGING_TOOL_CMD} install ${EXTRA_INSTALL_FLAGS} --upgrade 
${RESOLUTION_HIGHEST_FLAG} \
             ${ADDITIONAL_PIP_INSTALL_FLAGS} \
             ${ADDITIONAL_PYTHON_DEPS} ${EAGER_UPGRADE_ADDITIONAL_REQUIREMENTS=}
-        common::install_pip_version
+        common::install_packaging_tool
         set +x
         echo
         echo "${COLOR_BLUE}Running 'pip check'${COLOR_RESET}"
@@ -681,10 +736,10 @@ function install_additional_dependencies() {
         echo "${COLOR_BLUE}Installing additional dependencies upgrading only 
if needed${COLOR_RESET}"
         echo
         set -x
-        pip install --root-user-action ignore --upgrade --upgrade-strategy 
only-if-needed \
+        ${PACKAGING_TOOL_CMD} install ${EXTRA_INSTALL_FLAGS} --upgrade 
"${RESOLUTION_LOWEST_DIRECT_FLAG}" \
             ${ADDITIONAL_PIP_INSTALL_FLAGS} \
             ${ADDITIONAL_PYTHON_DEPS}
-        common::install_pip_version
+        common::install_packaging_tool
         set +x
         echo
         echo "${COLOR_BLUE}Running 'pip check'${COLOR_RESET}"
@@ -694,10 +749,11 @@ function install_additional_dependencies() {
 }
 
 common::get_colors
+common::get_packaging_tool
 common::get_airflow_version_specification
 common::override_pip_version_if_needed
 common::get_constraints_location
-common::show_pip_version_and_location
+common::show_packaging_tool_version_and_location
 
 install_additional_dependencies
 EOF
@@ -893,8 +949,12 @@ function check_boto_upgrade() {
     echo
     echo "${COLOR_BLUE}Upgrading boto3, botocore to latest version to run 
Amazon tests with them${COLOR_RESET}"
     echo
-    pip uninstall --root-user-action ignore aiobotocore s3fs -y || true
-    pip install --root-user-action ignore --upgrade boto3 botocore
+    # shellcheck disable=SC2086
+    ${PACKAGING_TOOL_CMD} uninstall ${EXTRA_UNINSTALL_FLAGS} aiobotocore s3fs 
|| true
+    # We need to include oss2 as dependency as otherwise jmespath will be 
bumped and it will not pass
+    # the pip check test, Similarly gcloud-aio-auth limit is needed to be 
included as it bumps cryptography
+    # shellcheck disable=SC2086
+    ${PACKAGING_TOOL_CMD} install ${EXTRA_INSTALL_FLAGS} --upgrade boto3 
botocore "oss2>=2.14.0" "gcloud-aio-auth>=4.0.0,<5.0.0"
     pip check
 }
 
@@ -903,25 +963,31 @@ function check_pydantic() {
         echo
         echo "${COLOR_YELLOW}Reinstalling airflow from local sources to 
account for pyproject.toml changes${COLOR_RESET}"
         echo
-        pip install --root-user-action ignore -e .
+        # shellcheck disable=SC2086
+        ${PACKAGING_TOOL_CMD} install ${EXTRA_INSTALL_FLAGS} -e .
         echo
         echo "${COLOR_YELLOW}Remove pydantic and 3rd party libraries that 
depend on it${COLOR_RESET}"
         echo
-        pip uninstall --root-user-action ignore pydantic aws-sam-translator 
openai pyiceberg qdrant-client cfn-lint -y
+        # shellcheck disable=SC2086
+        ${PACKAGING_TOOL_CMD} uninstall ${EXTRA_UNINSTALL_FLAGS} pydantic 
aws-sam-translator openai \
+           pyiceberg qdrant-client cfn-lint weaviate-client
         pip check
     elif [[ ${PYDANTIC=} == "v1" ]]; then
         echo
         echo "${COLOR_YELLOW}Reinstalling airflow from local sources to 
account for pyproject.toml changes${COLOR_RESET}"
         echo
-        pip install --root-user-action ignore -e .
+        # shellcheck disable=SC2086
+        ${PACKAGING_TOOL_CMD} install ${EXTRA_INSTALL_FLAGS} -e .
         echo
-        echo "${COLOR_YELLOW}Uninstalling pyicberg which is not compatible 
with Pydantic 1${COLOR_RESET}"
+        echo "${COLOR_YELLOW}Uninstalling dependencies which are not 
compatible with Pydantic 1${COLOR_RESET}"
         echo
-        pip uninstall pyiceberg -y
+        # shellcheck disable=SC2086
+        ${PACKAGING_TOOL_CMD} uninstall ${EXTRA_UNINSTALL_FLAGS} pyiceberg 
waeviate-client
         echo
         echo "${COLOR_YELLOW}Downgrading Pydantic to < 2${COLOR_RESET}"
         echo
-        pip install --upgrade "pydantic<2.0.0"
+        # shellcheck disable=SC2086
+        ${PACKAGING_TOOL_CMD} install ${EXTRA_INSTALL_FLAGS} --upgrade 
"pydantic<2.0.0"
         pip check
     else
         echo
@@ -939,7 +1005,8 @@ function check_download_sqlalchemy() {
     echo
     echo "${COLOR_BLUE}Downgrading sqlalchemy to minimum supported version: 
${min_sqlalchemy_version}${COLOR_RESET}"
     echo
-    pip install --root-user-action ignore 
"sqlalchemy==${min_sqlalchemy_version}"
+    # shellcheck disable=SC2086
+    ${PACKAGING_TOOL_CMD} install ${EXTRA_INSTALL_FLAGS} 
"sqlalchemy==${min_sqlalchemy_version}"
     pip check
 }
 
@@ -951,7 +1018,8 @@ function check_download_pendulum() {
     echo
     echo "${COLOR_BLUE}Downgrading pendulum to minimum supported version: 
${min_pendulum_version}${COLOR_RESET}"
     echo
-    pip install --root-user-action ignore "pendulum==${min_pendulum_version}"
+    # shellcheck disable=SC2086
+    ${PACKAGING_TOOL_CMD} install ${EXTRA_INSTALL_FLAGS} 
"pendulum==${min_pendulum_version}"
     pip check
 }
 
@@ -1042,10 +1110,14 @@ ENV DEV_APT_COMMAND=${DEV_APT_COMMAND} \
 COPY --from=scripts install_os_dependencies.sh /scripts/docker/
 RUN bash /scripts/docker/install_os_dependencies.sh dev
 
-# THE 3 LINES ARE ONLY NEEDED IN ORDER TO MAKE PYMSSQL BUILD WORK WITH LATEST 
CYTHON
+# THE 7 LINES ARE ONLY NEEDED IN ORDER TO MAKE PYMSSQL BUILD WORK WITH LATEST 
CYTHON
 # AND SHOULD BE REMOVED WHEN WORKAROUND IN install_mssql.sh IS REMOVED
 ARG AIRFLOW_PIP_VERSION=24.0
-ENV AIRFLOW_PIP_VERSION=${AIRFLOW_PIP_VERSION}
+ARG AIRFLOW_UV_VERSION=0.1.10
+ARG AIRFLOW_USE_UV="true"
+ENV AIRFLOW_PIP_VERSION=${AIRFLOW_PIP_VERSION} \
+    AIRFLOW_UV_VERSION=${AIRFLOW_UV_VERSION} \
+    AIRFLOW_USE_UV=${AIRFLOW_USE_UV}
 COPY --from=scripts common.sh /scripts/docker/
 
 # Only copy mysql/mssql installation scripts for now - so that changing the 
other
@@ -1108,9 +1180,13 @@ ARG DEFAULT_CONSTRAINTS_BRANCH="constraints-main"
 ARG AIRFLOW_CI_BUILD_EPOCH="10"
 ARG AIRFLOW_PRE_CACHED_PIP_PACKAGES="true"
 ARG AIRFLOW_PIP_VERSION=24.0
+ARG AIRFLOW_UV_VERSION=0.1.10
+ARG AIRFLOW_USE_UV="true"
 # Setup PIP
 # By default PIP install run without cache to make image smaller
 ARG PIP_NO_CACHE_DIR="true"
+# By default UV install run without cache to make image smaller
+ARG UV_NO_CACHE="true"
 # By default PIP has progress bar but you can disable it.
 ARG PIP_PROGRESS_BAR="on"
 # Optimizing installation of Cassandra driver (in case there are no prebuilt 
wheels which is the
@@ -1138,6 +1214,8 @@ ENV AIRFLOW_REPO=${AIRFLOW_REPO}\
     AIRFLOW_PRE_CACHED_PIP_PACKAGES=${AIRFLOW_PRE_CACHED_PIP_PACKAGES} \
     AIRFLOW_VERSION=${AIRFLOW_VERSION} \
     AIRFLOW_PIP_VERSION=${AIRFLOW_PIP_VERSION} \
+    AIRFLOW_UV_VERSION=${AIRFLOW_UV_VERSION} \
+    AIRFLOW_USE_UV=${AIRFLOW_USE_UV} \
 # In the CI image we always:
 # * install MySQL, MsSQL
 # * install airflow from current sources, not from PyPI package
@@ -1153,6 +1231,7 @@ ENV AIRFLOW_REPO=${AIRFLOW_REPO}\
     AIRFLOW_VERSION_SPECIFICATION="" \
     PIP_NO_CACHE_DIR=${PIP_NO_CACHE_DIR} \
     PIP_PROGRESS_BAR=${PIP_PROGRESS_BAR} \
+    UV_NO_CACHE=${UV_NO_CACHE} \
     ADDITIONAL_PIP_INSTALL_FLAGS=${ADDITIONAL_PIP_INSTALL_FLAGS} \
     CASS_DRIVER_BUILD_CONCURRENCY=${CASS_DRIVER_BUILD_CONCURRENCY} \
     CASS_DRIVER_NO_CYTHON=${CASS_DRIVER_NO_CYTHON}
diff --git a/dev/breeze/doc/ci/02_images.md b/dev/breeze/doc/ci/02_images.md
index 4c6fc2c016..7807d94b1e 100644
--- a/dev/breeze/doc/ci/02_images.md
+++ b/dev/breeze/doc/ci/02_images.md
@@ -428,6 +428,7 @@ can be used for CI images:
 | `DEPENDENCIES_EPOCH_NUMBER`       | `2`                                      
                               | increasing this number will reinstall all apt 
dependencies                                                                    
                             |
 | `ADDITIONAL_PIP_INSTALL_FLAGS`    |                                          
                               | additional `pip` flags passed to the 
installation commands (except when reinstalling `pip` itself)                   
                                      |
 | `PIP_NO_CACHE_DIR`                | `true`                                   
                               | if true, then no pip cache will be stored      
                                                                                
                            |
+| `UV_NO_CACHE`                     | `true`                                   
                               | if true, then no uv cache will be stored       
                                                                                
                            |
 | `HOME`                            | `/root`                                  
                               | Home directory of the root user (CI image has 
root user as default)                                                           
                             |
 | `AIRFLOW_HOME`                    | `/root/airflow`                          
                               | Airflow's HOME (that's where logs and sqlite 
databases are stored)                                                           
                              |
 | `AIRFLOW_SOURCES`                 | `/opt/airflow`                           
                               | Mounted sources of Airflow                     
                                                                                
                            |
@@ -447,6 +448,8 @@ can be used for CI images:
 | `ADDITIONAL_DEV_APT_DEPS`         |                                          
                               | Additional apt dev dependencies installed in 
the first part of the image                                                     
                              |
 | `ADDITIONAL_DEV_APT_ENV`          |                                          
                               | Additional env variables defined when 
installing dev deps                                                             
                                     |
 | `AIRFLOW_PIP_VERSION`             | `24.0`                                   
                               | PIP version used.                              
                                                                                
                            |
+| `AIRFLOW_UV_VERSION`              | `0.1.10`                                 
                               | UV version used.                               
                                                                                
                            |
+| `AIRFLOW_USE_UV`                  | `true`                                   
                               | Whether to use UV for installation.            
                                                                                
                            |
 | `PIP_PROGRESS_BAR`                | `on`                                     
                               | Progress bar for PIP installation              
                                                                                
                            |
 
 Here are some examples of how CI images can built manually. CI is always
diff --git a/dev/breeze/doc/images/output_ci-image_build.svg 
b/dev/breeze/doc/images/output_ci-image_build.svg
index 17ea15e9e9..f617eb05e7 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 2343.6" 
xmlns="http://www.w3.org/2000/svg";>
+<svg class="rich-terminal" viewBox="0 0 1482 2368.0" 
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="2292.6" />
+      <rect x="0" y="0" width="1463.0" height="2317.0" />
     </clipPath>
     <clipPath id="breeze-ci-image-build-line-0">
     <rect x="0" y="1.5" width="1464" height="24.65"/>
@@ -324,9 +324,12 @@
 <clipPath id="breeze-ci-image-build-line-92">
     <rect x="0" y="2246.3" width="1464" height="24.65"/>
             </clipPath>
+<clipPath id="breeze-ci-image-build-line-93">
+    <rect x="0" y="2270.7" 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="2341.6" 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="2366" 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"/>
@@ -387,49 +390,50 @@
 </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="12.2" 
clip-path="url(#breeze-ci-image-build-line-48)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="1191.2" textLength="85.4" 
clip-path="url(#breeze-ci-image-build-line-48)">-python</text><text 
class="breeze-ci-image-build-r4" x="122" y="1191.2" textLength="73.2" clip-pa 
[...]
 </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-r1" x="439.2" y="1215.6" textLength="1000.4" 
clip-path="url(#breeze-ci-image-build-line-49)">something&#160;like:&#160;python:VERSION-slim-bookworm.&#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="1240" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-50)">│</text><text 
class="breeze-ci-image-build-r7" x="439.2" y="1240" textLength="1000.4" 
clip-path="url(#breeze-ci-image-build-line-50)">(TEXT)&#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;&#160;&#1
 [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="1264.4" 
textLength="1464" 
clip-path="url(#breeze-ci-image-build-line-51)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-ci-image-build-r1" x="1464" y="1264.4" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-51)">
-</text><text class="breeze-ci-image-build-r5" x="0" y="1288.8" 
textLength="24.4" 
clip-path="url(#breeze-ci-image-build-line-52)">╭─</text><text 
class="breeze-ci-image-build-r5" x="24.4" y="1288.8" textLength="597.8" 
clip-path="url(#breeze-ci-image-build-line-52)">&#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="1288.8" textLength="817.4" 
clip-path="url(#breeze-ci-image-build-line-52)">────────── [...]
-</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="12.2" 
clip-path="url(#breeze-ci-image-build-line-53)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="1313.2" textLength="97.6" 
clip-path="url(#breeze-ci-image-build-line-53)">-airflow</text><text 
class="breeze-ci-image-build-r4" x="134.2" y="1313.2" textLength="256.2" cli 
[...]
-</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="12.2" 
clip-path="url(#breeze-ci-image-build-line-54)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="1337.6" textLength="97.6" 
clip-path="url(#breeze-ci-image-build-line-54)">-airflow</text><text 
class="breeze-ci-image-build-r4" x="134.2" y="1337.6" textLength="207.4" cli 
[...]
-</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-r7" x="451.4" y="1362" textLength="866.2" 
clip-path="url(#breeze-ci-image-build-line-55)">(constraints-source-providers&#160;|&#160;constraints&#160;|&#160;constraints-no-providers)</text><text
 class="breeze-ci-image-build-r5" x="1451.8" y="1362" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-55)">│</text>< [...]
-</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-r5" x="451.4" y="1386.4" textLength="866.2" 
clip-path="url(#breeze-ci-image-build-line-56)">[default:&#160;constraints-source-providers]&#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="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="12.2" 
clip-path="url(#breeze-ci-image-build-line-57)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="1410.8" textLength="97.6" 
clip-path="url(#breeze-ci-image-build-line-57)">-airflow</text><text 
class="breeze-ci-image-build-r4" x="134.2" y="1410.8" textLength="268.4" cli 
[...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="1435.2" 
textLength="1464" 
clip-path="url(#breeze-ci-image-build-line-58)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-ci-image-build-r1" x="1464" y="1435.2" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-58)">
-</text><text class="breeze-ci-image-build-r5" x="0" y="1459.6" 
textLength="24.4" 
clip-path="url(#breeze-ci-image-build-line-59)">╭─</text><text 
class="breeze-ci-image-build-r5" x="24.4" y="1459.6" textLength="634.4" 
clip-path="url(#breeze-ci-image-build-line-59)">&#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="1459.6" textLength="780.8" 
clip-path="url(#breeze-ci-image-build-line-59)">── [...]
-</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="12.2" 
clip-path="url(#breeze-ci-image-build-line-60)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="1484" textLength="134.2" 
clip-path="url(#breeze-ci-image-build-line-60)">-additional</text><text 
class="breeze-ci-image-build-r4" x="170.8" y="1484" textLength="183" clip-path 
[...]
-</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="12.2" 
clip-path="url(#breeze-ci-image-build-line-61)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="1508.4" textLength="134.2" 
clip-path="url(#breeze-ci-image-build-line-61)">-additional</text><text 
class="breeze-ci-image-build-r4" x="170.8" y="1508.4" textLength="146.4" [...]
-</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="12.2" 
clip-path="url(#breeze-ci-image-build-line-62)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="1532.8" textLength="48.8" 
clip-path="url(#breeze-ci-image-build-line-62)">-dev</text><text 
class="breeze-ci-image-build-r4" x="85.4" y="1532.8" textLength="109.8" 
clip-pat [...]
-</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-r4" x="24.4" y="1557.2" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-63)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="1557.2" textLength="134.2" 
clip-path="url(#breeze-ci-image-build-line-63)">-additional</text><text 
class="breeze-ci-image-build-r4" x="170.8" y="1557.2" textLength="158.6" [...]
-</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="12.2" 
clip-path="url(#breeze-ci-image-build-line-64)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="1581.6" textLength="48.8" 
clip-path="url(#breeze-ci-image-build-line-64)">-dev</text><text 
class="breeze-ci-image-build-r4" x="85.4" y="1581.6" textLength="146.4" 
clip-pat [...]
-</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="12.2" 
clip-path="url(#breeze-ci-image-build-line-65)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="1606" textLength="134.2" 
clip-path="url(#breeze-ci-image-build-line-65)">-additional</text><text 
class="breeze-ci-image-build-r4" x="170.8" y="1606" textLength="195.2" clip-pa 
[...]
-</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-r4" x="24.4" y="1630.4" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-66)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="1630.4" textLength="134.2" 
clip-path="url(#breeze-ci-image-build-line-66)">-additional</text><text 
class="breeze-ci-image-build-r4" x="170.8" y="1630.4" textLength="146.4" [...]
-</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="268.4" 
clip-path="url(#breeze-ci-image-build-line-68)">&#160;Backtracking&#160;options&#160;</text><text
 class="breeze-ci-image-build-r5" x="292.8" y="1679.2" textLength="1146.8" 
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="12.2" 
clip-path="url(#breeze-ci-image-build-line-69)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="1703.6" textLength="73.2" 
clip-path="url(#breeze-ci-image-build-line-69)">-build</text><text 
class="breeze-ci-image-build-r4" x="109.8" y="1703.6" textLength="195.2" clip- 
[...]
-</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-r1" x="549" y="1728" textLength="890.6" 
clip-path="url(#breeze-ci-image-build-line-70)">backtracking&#160;problems.&#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;&#1
 [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="1752.4" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-71)">│</text><text 
class="breeze-ci-image-build-r7" x="549" y="1752.4" textLength="890.6" 
clip-path="url(#breeze-ci-image-build-line-71)">(INTEGER)&#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;&#160
 [...]
-</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="12.2" 
clip-path="url(#breeze-ci-image-build-line-72)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="1776.8" textLength="73.2" 
clip-path="url(#breeze-ci-image-build-line-72)">-eager</text><text 
class="breeze-ci-image-build-r4" x="109.8" y="1776.8" textLength="390.4" clip- 
[...]
-</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-r1" x="549" y="1801.2" textLength="890.6" 
clip-path="url(#breeze-ci-image-build-line-73)">(see&#160;`breeze&#160;ci&#160;find-backtracking-candidates`).&#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;</text><text
 [...]
-</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-r7" x="549" y="1825.6" textLength="890.6" 
clip-path="url(#breeze-ci-image-build-line-74)">(TEXT)&#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;&#160;&#
 [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="1850" textLength="1464" 
clip-path="url(#breeze-ci-image-build-line-75)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-ci-image-build-r1" x="1464" y="1850" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-75)">
-</text><text class="breeze-ci-image-build-r5" x="0" y="1874.4" 
textLength="24.4" 
clip-path="url(#breeze-ci-image-build-line-76)">╭─</text><text 
class="breeze-ci-image-build-r5" x="24.4" y="1874.4" textLength="622.2" 
clip-path="url(#breeze-ci-image-build-line-76)">&#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="1874.4" textLength="793" 
clip-path="url(#breeze-ci-image-build-line-76)"> [...]
-</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="12.2" 
clip-path="url(#breeze-ci-image-build-line-77)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="1898.8" textLength="97.6" 
clip-path="url(#breeze-ci-image-build-line-77)">-builder</text><text 
class="breeze-ci-image-build-r1" x="341.6" y="1898.8" textLength="756.4" cli 
[...]
-</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-r5" x="341.6" y="1923.2" textLength="756.4" 
clip-path="url(#breeze-ci-image-build-line-78)">[default:&#160;autodetect]&#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="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="12.2" 
clip-path="url(#breeze-ci-image-build-line-79)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="1947.6" textLength="109.8" 
clip-path="url(#breeze-ci-image-build-line-79)">-platform</text><text 
class="breeze-ci-image-build-r1" x="341.6" y="1947.6" textLength="329.4" c [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="1972" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-80)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1972" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-80)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="1972" textLength="61" 
clip-path="url(#breeze-ci-image-build-line-80)">-push</text><text 
class="breeze-ci-image-build-r1" x="341.6" y="1972" textLength="353.8" 
clip-path="url(# [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="1996.4" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-81)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1996.4" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-81)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="1996.4" textLength="97.6" 
clip-path="url(#breeze-ci-image-build-line-81)">-prepare</text><text 
class="breeze-ci-image-build-r4" x="134.2" y="1996.4" textLength="158.6" cli 
[...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="2020.8" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-82)">│</text><text 
class="breeze-ci-image-build-r1" x="341.6" y="2020.8" textLength="1098" 
clip-path="url(#breeze-ci-image-build-line-82)">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;&#160;
 [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="2045.2" 
textLength="1464" 
clip-path="url(#breeze-ci-image-build-line-83)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-ci-image-build-r1" x="1464" y="2045.2" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-83)">
-</text><text class="breeze-ci-image-build-r5" x="0" y="2069.6" 
textLength="24.4" 
clip-path="url(#breeze-ci-image-build-line-84)">╭─</text><text 
class="breeze-ci-image-build-r5" x="24.4" y="2069.6" textLength="280.6" 
clip-path="url(#breeze-ci-image-build-line-84)">&#160;Github&#160;authentication&#160;</text><text
 class="breeze-ci-image-build-r5" x="305" y="2069.6" textLength="1134.6" 
clip-path="url(#breeze-ci-image-build-line-84)">─────────────────────────────────────────────────────────
 [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="2094" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-85)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="2094" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-85)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="2094" textLength="85.4" 
clip-path="url(#breeze-ci-image-build-line-85)">-github</text><text 
class="breeze-ci-image-build-r4" x="122" y="2094" textLength="134.2" 
clip-path="url [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="2118.4" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-86)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="2118.4" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-86)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="2118.4" textLength="85.4" 
clip-path="url(#breeze-ci-image-build-line-86)">-github</text><text 
class="breeze-ci-image-build-r4" x="122" y="2118.4" textLength="73.2" clip-pa 
[...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="2142.8" 
textLength="1464" 
clip-path="url(#breeze-ci-image-build-line-87)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-ci-image-build-r1" x="1464" y="2142.8" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-87)">
-</text><text class="breeze-ci-image-build-r5" x="0" y="2167.2" 
textLength="24.4" 
clip-path="url(#breeze-ci-image-build-line-88)">╭─</text><text 
class="breeze-ci-image-build-r5" x="24.4" y="2167.2" textLength="195.2" 
clip-path="url(#breeze-ci-image-build-line-88)">&#160;Common&#160;options&#160;</text><text
 class="breeze-ci-image-build-r5" x="219.6" y="2167.2" textLength="1220" 
clip-path="url(#breeze-ci-image-build-line-88)">────────────────────────────────────────────────────────────────
 [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="2191.6" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-89)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="2191.6" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-89)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="2191.6" textLength="85.4" 
clip-path="url(#breeze-ci-image-build-line-89)">-answer</text><text 
class="breeze-ci-image-build-r6" x="158.6" y="2191.6" textLength="24.4" clip- 
[...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="2216" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-90)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="2216" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-90)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="2216" textLength="48.8" 
clip-path="url(#breeze-ci-image-build-line-90)">-dry</text><text 
class="breeze-ci-image-build-r4" x="85.4" y="2216" textLength="48.8" 
clip-path="url(#b [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="2240.4" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-91)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="2240.4" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-91)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="2240.4" textLength="97.6" 
clip-path="url(#breeze-ci-image-build-line-91)">-verbose</text><text 
class="breeze-ci-image-build-r6" x="158.6" y="2240.4" textLength="24.4" clip 
[...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="2264.8" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-92)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="2264.8" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-92)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="2264.8" textLength="61" 
clip-path="url(#breeze-ci-image-build-line-92)">-help</text><text 
class="breeze-ci-image-build-r6" x="158.6" y="2264.8" textLength="24.4" 
clip-path [...]
-</text><text class="breeze-ci-image-build-r5" x="0" y="2289.2" 
textLength="1464" 
clip-path="url(#breeze-ci-image-build-line-93)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-ci-image-build-r1" x="1464" y="2289.2" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-93)">
+</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-r4" x="24.4" y="1264.4" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-51)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="1264.4" textLength="48.8" 
clip-path="url(#breeze-ci-image-build-line-51)">-use</text><text 
class="breeze-ci-image-build-r4" x="85.4" y="1264.4" textLength="36.6" 
clip-path [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="1288.8" 
textLength="1464" 
clip-path="url(#breeze-ci-image-build-line-52)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-ci-image-build-r1" x="1464" y="1288.8" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-52)">
+</text><text class="breeze-ci-image-build-r5" x="0" y="1313.2" 
textLength="24.4" 
clip-path="url(#breeze-ci-image-build-line-53)">╭─</text><text 
class="breeze-ci-image-build-r5" x="24.4" y="1313.2" textLength="597.8" 
clip-path="url(#breeze-ci-image-build-line-53)">&#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="1313.2" textLength="817.4" 
clip-path="url(#breeze-ci-image-build-line-53)">────────── [...]
+</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="12.2" 
clip-path="url(#breeze-ci-image-build-line-54)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="1337.6" textLength="97.6" 
clip-path="url(#breeze-ci-image-build-line-54)">-airflow</text><text 
class="breeze-ci-image-build-r4" x="134.2" y="1337.6" textLength="256.2" cli 
[...]
+</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="12.2" 
clip-path="url(#breeze-ci-image-build-line-55)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="1362" textLength="97.6" 
clip-path="url(#breeze-ci-image-build-line-55)">-airflow</text><text 
class="breeze-ci-image-build-r4" x="134.2" y="1362" textLength="207.4" 
clip-path=" [...]
+</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-r7" x="451.4" y="1386.4" textLength="866.2" 
clip-path="url(#breeze-ci-image-build-line-56)">(constraints-source-providers&#160;|&#160;constraints&#160;|&#160;constraints-no-providers)</text><text
 class="breeze-ci-image-build-r5" x="1451.8" y="1386.4" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-56)">│</ [...]
+</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-r5" x="451.4" y="1410.8" textLength="866.2" 
clip-path="url(#breeze-ci-image-build-line-57)">[default:&#160;constraints-source-providers]&#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="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="12.2" 
clip-path="url(#breeze-ci-image-build-line-58)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="1435.2" textLength="97.6" 
clip-path="url(#breeze-ci-image-build-line-58)">-airflow</text><text 
class="breeze-ci-image-build-r4" x="134.2" y="1435.2" textLength="268.4" cli 
[...]
+</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="634.4" 
clip-path="url(#breeze-ci-image-build-line-60)">&#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="1484" textLength="780.8" 
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="12.2" 
clip-path="url(#breeze-ci-image-build-line-61)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="1508.4" textLength="134.2" 
clip-path="url(#breeze-ci-image-build-line-61)">-additional</text><text 
class="breeze-ci-image-build-r4" x="170.8" y="1508.4" textLength="183" c [...]
+</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="12.2" 
clip-path="url(#breeze-ci-image-build-line-62)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="1532.8" textLength="134.2" 
clip-path="url(#breeze-ci-image-build-line-62)">-additional</text><text 
class="breeze-ci-image-build-r4" x="170.8" y="1532.8" textLength="146.4" [...]
+</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-r4" x="24.4" y="1557.2" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-63)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="1557.2" textLength="48.8" 
clip-path="url(#breeze-ci-image-build-line-63)">-dev</text><text 
class="breeze-ci-image-build-r4" x="85.4" y="1557.2" textLength="109.8" 
clip-pat [...]
+</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="12.2" 
clip-path="url(#breeze-ci-image-build-line-64)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="1581.6" textLength="134.2" 
clip-path="url(#breeze-ci-image-build-line-64)">-additional</text><text 
class="breeze-ci-image-build-r4" x="170.8" y="1581.6" textLength="158.6" [...]
+</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="12.2" 
clip-path="url(#breeze-ci-image-build-line-65)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="1606" textLength="48.8" 
clip-path="url(#breeze-ci-image-build-line-65)">-dev</text><text 
class="breeze-ci-image-build-r4" x="85.4" y="1606" textLength="146.4" 
clip-path="url(# [...]
+</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-r4" x="24.4" y="1630.4" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-66)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="1630.4" textLength="134.2" 
clip-path="url(#breeze-ci-image-build-line-66)">-additional</text><text 
class="breeze-ci-image-build-r4" x="170.8" y="1630.4" textLength="195.2" [...]
+</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="12.2" 
clip-path="url(#breeze-ci-image-build-line-67)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="1654.8" textLength="134.2" 
clip-path="url(#breeze-ci-image-build-line-67)">-additional</text><text 
class="breeze-ci-image-build-r4" x="170.8" y="1654.8" textLength="146.4" [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="1679.2" 
textLength="1464" 
clip-path="url(#breeze-ci-image-build-line-68)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-ci-image-build-r1" x="1464" y="1679.2" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-68)">
+</text><text class="breeze-ci-image-build-r5" x="0" y="1703.6" 
textLength="24.4" 
clip-path="url(#breeze-ci-image-build-line-69)">╭─</text><text 
class="breeze-ci-image-build-r5" x="24.4" y="1703.6" textLength="268.4" 
clip-path="url(#breeze-ci-image-build-line-69)">&#160;Backtracking&#160;options&#160;</text><text
 class="breeze-ci-image-build-r5" x="292.8" y="1703.6" textLength="1146.8" 
clip-path="url(#breeze-ci-image-build-line-69)">────────────────────────────────────────────────────────
 [...]
+</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="12.2" 
clip-path="url(#breeze-ci-image-build-line-70)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="1728" textLength="73.2" 
clip-path="url(#breeze-ci-image-build-line-70)">-build</text><text 
class="breeze-ci-image-build-r4" x="109.8" y="1728" textLength="195.2" 
clip-path="ur [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="1752.4" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-71)">│</text><text 
class="breeze-ci-image-build-r1" x="549" y="1752.4" textLength="890.6" 
clip-path="url(#breeze-ci-image-build-line-71)">backtracking&#160;problems.&#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
 [...]
+</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-r7" x="549" y="1776.8" textLength="890.6" 
clip-path="url(#breeze-ci-image-build-line-72)">(INTEGER)&#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;&#160
 [...]
+</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="12.2" 
clip-path="url(#breeze-ci-image-build-line-73)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="1801.2" textLength="73.2" 
clip-path="url(#breeze-ci-image-build-line-73)">-eager</text><text 
class="breeze-ci-image-build-r4" x="109.8" y="1801.2" textLength="390.4" clip- 
[...]
+</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-r1" x="549" y="1825.6" textLength="890.6" 
clip-path="url(#breeze-ci-image-build-line-74)">(see&#160;`breeze&#160;ci&#160;find-backtracking-candidates`).&#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;</text><text
 [...]
+</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-r7" x="549" y="1850" textLength="890.6" 
clip-path="url(#breeze-ci-image-build-line-75)">(TEXT)&#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;&#160;&#160;
 [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="1874.4" 
textLength="1464" 
clip-path="url(#breeze-ci-image-build-line-76)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-ci-image-build-r1" x="1464" y="1874.4" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-76)">
+</text><text class="breeze-ci-image-build-r5" x="0" y="1898.8" 
textLength="24.4" 
clip-path="url(#breeze-ci-image-build-line-77)">╭─</text><text 
class="breeze-ci-image-build-r5" x="24.4" y="1898.8" textLength="622.2" 
clip-path="url(#breeze-ci-image-build-line-77)">&#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="1898.8" textLength="793" 
clip-path="url(#breeze-ci-image-build-line-77)"> [...]
+</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="12.2" 
clip-path="url(#breeze-ci-image-build-line-78)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="1923.2" textLength="97.6" 
clip-path="url(#breeze-ci-image-build-line-78)">-builder</text><text 
class="breeze-ci-image-build-r1" x="341.6" y="1923.2" textLength="756.4" cli 
[...]
+</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-r5" x="341.6" y="1947.6" textLength="756.4" 
clip-path="url(#breeze-ci-image-build-line-79)">[default:&#160;autodetect]&#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="1972" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-80)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1972" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-80)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="1972" textLength="109.8" 
clip-path="url(#breeze-ci-image-build-line-80)">-platform</text><text 
class="breeze-ci-image-build-r1" x="341.6" y="1972" textLength="329.4" 
clip-path [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="1996.4" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-81)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="1996.4" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-81)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="1996.4" textLength="61" 
clip-path="url(#breeze-ci-image-build-line-81)">-push</text><text 
class="breeze-ci-image-build-r1" x="341.6" y="1996.4" textLength="353.8" 
clip-pat [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="2020.8" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-82)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="2020.8" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-82)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="2020.8" textLength="97.6" 
clip-path="url(#breeze-ci-image-build-line-82)">-prepare</text><text 
class="breeze-ci-image-build-r4" x="134.2" y="2020.8" textLength="158.6" cli 
[...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="2045.2" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-83)">│</text><text 
class="breeze-ci-image-build-r1" x="341.6" y="2045.2" textLength="1098" 
clip-path="url(#breeze-ci-image-build-line-83)">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;&#160;
 [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="2069.6" 
textLength="1464" 
clip-path="url(#breeze-ci-image-build-line-84)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-ci-image-build-r1" x="1464" y="2069.6" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-84)">
+</text><text class="breeze-ci-image-build-r5" x="0" y="2094" textLength="24.4" 
clip-path="url(#breeze-ci-image-build-line-85)">╭─</text><text 
class="breeze-ci-image-build-r5" x="24.4" y="2094" textLength="280.6" 
clip-path="url(#breeze-ci-image-build-line-85)">&#160;Github&#160;authentication&#160;</text><text
 class="breeze-ci-image-build-r5" x="305" y="2094" textLength="1134.6" 
clip-path="url(#breeze-ci-image-build-line-85)">───────────────────────────────────────────────────────────────
 [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="2118.4" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-86)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="2118.4" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-86)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="2118.4" textLength="85.4" 
clip-path="url(#breeze-ci-image-build-line-86)">-github</text><text 
class="breeze-ci-image-build-r4" x="122" y="2118.4" textLength="134.2" clip-p 
[...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="2142.8" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-87)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="2142.8" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-87)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="2142.8" textLength="85.4" 
clip-path="url(#breeze-ci-image-build-line-87)">-github</text><text 
class="breeze-ci-image-build-r4" x="122" y="2142.8" textLength="73.2" clip-pa 
[...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="2167.2" 
textLength="1464" 
clip-path="url(#breeze-ci-image-build-line-88)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-ci-image-build-r1" x="1464" y="2167.2" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-88)">
+</text><text class="breeze-ci-image-build-r5" x="0" y="2191.6" 
textLength="24.4" 
clip-path="url(#breeze-ci-image-build-line-89)">╭─</text><text 
class="breeze-ci-image-build-r5" x="24.4" y="2191.6" textLength="195.2" 
clip-path="url(#breeze-ci-image-build-line-89)">&#160;Common&#160;options&#160;</text><text
 class="breeze-ci-image-build-r5" x="219.6" y="2191.6" textLength="1220" 
clip-path="url(#breeze-ci-image-build-line-89)">────────────────────────────────────────────────────────────────
 [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="2216" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-90)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="2216" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-90)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="2216" textLength="85.4" 
clip-path="url(#breeze-ci-image-build-line-90)">-answer</text><text 
class="breeze-ci-image-build-r6" x="158.6" y="2216" textLength="24.4" 
clip-path="ur [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="2240.4" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-91)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="2240.4" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-91)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="2240.4" textLength="48.8" 
clip-path="url(#breeze-ci-image-build-line-91)">-dry</text><text 
class="breeze-ci-image-build-r4" x="85.4" y="2240.4" textLength="48.8" 
clip-path [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="2264.8" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-92)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="2264.8" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-92)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="2264.8" textLength="97.6" 
clip-path="url(#breeze-ci-image-build-line-92)">-verbose</text><text 
class="breeze-ci-image-build-r6" x="158.6" y="2264.8" textLength="24.4" clip 
[...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="2289.2" 
textLength="12.2" clip-path="url(#breeze-ci-image-build-line-93)">│</text><text 
class="breeze-ci-image-build-r4" x="24.4" y="2289.2" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-93)">-</text><text 
class="breeze-ci-image-build-r4" x="36.6" y="2289.2" textLength="61" 
clip-path="url(#breeze-ci-image-build-line-93)">-help</text><text 
class="breeze-ci-image-build-r6" x="158.6" y="2289.2" textLength="24.4" 
clip-path [...]
+</text><text class="breeze-ci-image-build-r5" x="0" y="2313.6" 
textLength="1464" 
clip-path="url(#breeze-ci-image-build-line-94)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-ci-image-build-r1" x="1464" y="2313.6" textLength="12.2" 
clip-path="url(#breeze-ci-image-build-line-94)">
 </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 b4ab5dac08..b59fb9fc0b 100644
--- a/dev/breeze/doc/images/output_ci-image_build.txt
+++ b/dev/breeze/doc/images/output_ci-image_build.txt
@@ -1 +1 @@
-d2ef2733519d945c8cfd4fed63a43f24
+f535999147ac00393852eb3b28d7125b
diff --git a/dev/breeze/doc/images/output_prod-image_build.svg 
b/dev/breeze/doc/images/output_prod-image_build.svg
index 2e6599bb57..be7de879be 100644
--- a/dev/breeze/doc/images/output_prod-image_build.svg
+++ b/dev/breeze/doc/images/output_prod-image_build.svg
@@ -393,15 +393,15 @@
 </text><text class="breeze-prod-image-build-r5" x="0" y="752" 
textLength="12.2" 
clip-path="url(#breeze-prod-image-build-line-30)">│</text><text 
class="breeze-prod-image-build-r4" x="24.4" y="752" textLength="12.2" 
clip-path="url(#breeze-prod-image-build-line-30)">-</text><text 
class="breeze-prod-image-build-r4" x="36.6" y="752" textLength="97.6" 
clip-path="url(#breeze-prod-image-build-line-30)">-include</text><text 
class="breeze-prod-image-build-r4" x="134.2" y="752" textLength="195.2" c [...]
 </text><text class="breeze-prod-image-build-r5" x="0" y="776.4" 
textLength="1464" 
clip-path="url(#breeze-prod-image-build-line-31)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-prod-image-build-r1" x="1464" y="776.4" textLength="12.2" 
clip-path="url(#breeze-prod-image-build-line-31)">
 </text><text class="breeze-prod-image-build-r5" x="0" y="800.8" 
textLength="24.4" 
clip-path="url(#breeze-prod-image-build-line-32)">╭─</text><text 
class="breeze-prod-image-build-r5" x="24.4" y="800.8" textLength="512.4" 
clip-path="url(#breeze-prod-image-build-line-32)">&#160;Advanced&#160;build&#160;options&#160;(for&#160;power&#160;users)&#160;</text><text
 class="breeze-prod-image-build-r5" x="536.8" y="800.8" textLength="902.8" 
clip-path="url(#breeze-prod-image-build-line-32)">──────── [...]
-</text><text class="breeze-prod-image-build-r5" x="0" y="825.2" 
textLength="12.2" 
clip-path="url(#breeze-prod-image-build-line-33)">│</text><text 
class="breeze-prod-image-build-r4" x="24.4" y="825.2" textLength="12.2" 
clip-path="url(#breeze-prod-image-build-line-33)">-</text><text 
class="breeze-prod-image-build-r4" x="36.6" y="825.2" textLength="85.4" 
clip-path="url(#breeze-prod-image-build-line-33)">-debian</text><text 
class="breeze-prod-image-build-r4" x="122" y="825.2" textLength="97. [...]
-</text><text class="breeze-prod-image-build-r5" x="0" y="849.6" 
textLength="12.2" 
clip-path="url(#breeze-prod-image-build-line-34)">│</text><text 
class="breeze-prod-image-build-r7" x="439.2" y="849.6" textLength="793" 
clip-path="url(#breeze-prod-image-build-line-34)">(bookworm&#160;|&#160;bullseye)&#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;&
 [...]
-</text><text class="breeze-prod-image-build-r5" x="0" y="874" 
textLength="12.2" 
clip-path="url(#breeze-prod-image-build-line-35)">│</text><text 
class="breeze-prod-image-build-r5" x="439.2" y="874" textLength="793" 
clip-path="url(#breeze-prod-image-build-line-35)">[default:&#160;bookworm]&#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;
 [...]
-</text><text class="breeze-prod-image-build-r5" x="0" y="898.4" 
textLength="12.2" 
clip-path="url(#breeze-prod-image-build-line-36)">│</text><text 
class="breeze-prod-image-build-r4" x="24.4" y="898.4" textLength="12.2" 
clip-path="url(#breeze-prod-image-build-line-36)">-</text><text 
class="breeze-prod-image-build-r4" x="36.6" y="898.4" textLength="85.4" 
clip-path="url(#breeze-prod-image-build-line-36)">-python</text><text 
class="breeze-prod-image-build-r4" x="122" y="898.4" textLength="73. [...]
-</text><text class="breeze-prod-image-build-r5" x="0" y="922.8" 
textLength="12.2" 
clip-path="url(#breeze-prod-image-build-line-37)">│</text><text 
class="breeze-prod-image-build-r1" x="439.2" y="922.8" textLength="1000.4" 
clip-path="url(#breeze-prod-image-build-line-37)">something&#160;like:&#160;python:VERSION-slim-bookworm.&#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-prod-image-build-r5" x="0" y="947.2" 
textLength="12.2" 
clip-path="url(#breeze-prod-image-build-line-38)">│</text><text 
class="breeze-prod-image-build-r7" x="439.2" y="947.2" textLength="1000.4" 
clip-path="url(#breeze-prod-image-build-line-38)">(TEXT)&#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
 [...]
-</text><text class="breeze-prod-image-build-r5" x="0" y="971.6" 
textLength="12.2" 
clip-path="url(#breeze-prod-image-build-line-39)">│</text><text 
class="breeze-prod-image-build-r4" x="24.4" y="971.6" textLength="12.2" 
clip-path="url(#breeze-prod-image-build-line-39)">-</text><text 
class="breeze-prod-image-build-r4" x="36.6" y="971.6" textLength="85.4" 
clip-path="url(#breeze-prod-image-build-line-39)">-commit</text><text 
class="breeze-prod-image-build-r4" x="122" y="971.6" textLength="48. [...]
-</text><text class="breeze-prod-image-build-r5" x="0" y="996" 
textLength="12.2" 
clip-path="url(#breeze-prod-image-build-line-40)">│</text><text 
class="breeze-prod-image-build-r4" x="24.4" y="996" textLength="12.2" 
clip-path="url(#breeze-prod-image-build-line-40)">-</text><text 
class="breeze-prod-image-build-r4" x="36.6" y="996" textLength="134.2" 
clip-path="url(#breeze-prod-image-build-line-40)">-additional</text><text 
class="breeze-prod-image-build-r4" x="170.8" y="996" textLength="219. [...]
-</text><text class="breeze-prod-image-build-r5" x="0" y="1020.4" 
textLength="12.2" 
clip-path="url(#breeze-prod-image-build-line-41)">│</text><text 
class="breeze-prod-image-build-r1" x="439.2" y="1020.4" textLength="1000.4" 
clip-path="url(#breeze-prod-image-build-line-41)">itself).&#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;&
 [...]
+</text><text class="breeze-prod-image-build-r5" x="0" y="825.2" 
textLength="12.2" 
clip-path="url(#breeze-prod-image-build-line-33)">│</text><text 
class="breeze-prod-image-build-r4" x="24.4" y="825.2" textLength="12.2" 
clip-path="url(#breeze-prod-image-build-line-33)">-</text><text 
class="breeze-prod-image-build-r4" x="36.6" y="825.2" textLength="134.2" 
clip-path="url(#breeze-prod-image-build-line-33)">-additional</text><text 
class="breeze-prod-image-build-r4" x="170.8" y="825.2" textLeng [...]
+</text><text class="breeze-prod-image-build-r5" x="0" y="849.6" 
textLength="12.2" 
clip-path="url(#breeze-prod-image-build-line-34)">│</text><text 
class="breeze-prod-image-build-r1" x="439.2" y="849.6" textLength="1000.4" 
clip-path="url(#breeze-prod-image-build-line-34)">itself).&#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;&#1
 [...]
+</text><text class="breeze-prod-image-build-r5" x="0" y="874" 
textLength="12.2" 
clip-path="url(#breeze-prod-image-build-line-35)">│</text><text 
class="breeze-prod-image-build-r7" x="439.2" y="874" textLength="1000.4" 
clip-path="url(#breeze-prod-image-build-line-35)">(TEXT)&#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;&#1
 [...]
+</text><text class="breeze-prod-image-build-r5" x="0" y="898.4" 
textLength="12.2" 
clip-path="url(#breeze-prod-image-build-line-36)">│</text><text 
class="breeze-prod-image-build-r4" x="24.4" y="898.4" textLength="12.2" 
clip-path="url(#breeze-prod-image-build-line-36)">-</text><text 
class="breeze-prod-image-build-r4" x="36.6" y="898.4" textLength="85.4" 
clip-path="url(#breeze-prod-image-build-line-36)">-commit</text><text 
class="breeze-prod-image-build-r4" x="122" y="898.4" textLength="48. [...]
+</text><text class="breeze-prod-image-build-r5" x="0" y="922.8" 
textLength="12.2" 
clip-path="url(#breeze-prod-image-build-line-37)">│</text><text 
class="breeze-prod-image-build-r4" x="24.4" y="922.8" textLength="12.2" 
clip-path="url(#breeze-prod-image-build-line-37)">-</text><text 
class="breeze-prod-image-build-r4" x="36.6" y="922.8" textLength="85.4" 
clip-path="url(#breeze-prod-image-build-line-37)">-debian</text><text 
class="breeze-prod-image-build-r4" x="122" y="922.8" textLength="97. [...]
+</text><text class="breeze-prod-image-build-r5" x="0" y="947.2" 
textLength="12.2" 
clip-path="url(#breeze-prod-image-build-line-38)">│</text><text 
class="breeze-prod-image-build-r7" x="439.2" y="947.2" textLength="793" 
clip-path="url(#breeze-prod-image-build-line-38)">(bookworm&#160;|&#160;bullseye)&#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;&
 [...]
+</text><text class="breeze-prod-image-build-r5" x="0" y="971.6" 
textLength="12.2" 
clip-path="url(#breeze-prod-image-build-line-39)">│</text><text 
class="breeze-prod-image-build-r5" x="439.2" y="971.6" textLength="793" 
clip-path="url(#breeze-prod-image-build-line-39)">[default:&#160;bookworm]&#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;&#
 [...]
+</text><text class="breeze-prod-image-build-r5" x="0" y="996" 
textLength="12.2" 
clip-path="url(#breeze-prod-image-build-line-40)">│</text><text 
class="breeze-prod-image-build-r4" x="24.4" y="996" textLength="12.2" 
clip-path="url(#breeze-prod-image-build-line-40)">-</text><text 
class="breeze-prod-image-build-r4" x="36.6" y="996" textLength="85.4" 
clip-path="url(#breeze-prod-image-build-line-40)">-python</text><text 
class="breeze-prod-image-build-r4" x="122" y="996" textLength="73.2" clip- [...]
+</text><text class="breeze-prod-image-build-r5" x="0" y="1020.4" 
textLength="12.2" 
clip-path="url(#breeze-prod-image-build-line-41)">│</text><text 
class="breeze-prod-image-build-r1" x="439.2" y="1020.4" textLength="1000.4" 
clip-path="url(#breeze-prod-image-build-line-41)">something&#160;like:&#160;python:VERSION-slim-bookworm.&#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;&#
 [...]
 </text><text class="breeze-prod-image-build-r5" x="0" y="1044.8" 
textLength="12.2" 
clip-path="url(#breeze-prod-image-build-line-42)">│</text><text 
class="breeze-prod-image-build-r7" x="439.2" y="1044.8" textLength="1000.4" 
clip-path="url(#breeze-prod-image-build-line-42)">(TEXT)&#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;&#1
 [...]
 </text><text class="breeze-prod-image-build-r5" x="0" y="1069.2" 
textLength="1464" 
clip-path="url(#breeze-prod-image-build-line-43)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-prod-image-build-r1" x="1464" y="1069.2" textLength="12.2" 
clip-path="url(#breeze-prod-image-build-line-43)">
 </text><text class="breeze-prod-image-build-r5" x="0" y="1093.6" 
textLength="24.4" 
clip-path="url(#breeze-prod-image-build-line-44)">╭─</text><text 
class="breeze-prod-image-build-r5" x="24.4" y="1093.6" textLength="597.8" 
clip-path="url(#breeze-prod-image-build-line-44)">&#160;Selecting&#160;constraint&#160;location&#160;(for&#160;power&#160;users)&#160;</text><text
 class="breeze-prod-image-build-r5" x="622.2" y="1093.6" textLength="817.4" 
clip-path="url(#breeze-prod-image-build-line-44) [...]
diff --git a/dev/breeze/doc/images/output_prod-image_build.txt 
b/dev/breeze/doc/images/output_prod-image_build.txt
index dbfb23f9c0..86876deb7f 100644
--- a/dev/breeze/doc/images/output_prod-image_build.txt
+++ b/dev/breeze/doc/images/output_prod-image_build.txt
@@ -1 +1 @@
-e3ed45363576b9bb3eb1cf5e3a2c08df
+07693b2597b00fdb156949c753dae783
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 103d4dafd5..8213555c0a 100644
--- a/dev/breeze/src/airflow_breeze/commands/ci_image_commands.py
+++ b/dev/breeze/src/airflow_breeze/commands/ci_image_commands.py
@@ -252,6 +252,14 @@ option_eager_upgrade_additional_requirements = 
click.option(
     "(see `breeze ci find-backtracking-candidates`).",
 )
 
+option_use_uv_ci = click.option(
+    "--use-uv/--no-use-uv",
+    is_flag=True,
+    default=True,
+    help="Use uv instead of pip as packaging tool.",
+    envvar="USE_UV",
+)
+
 option_upgrade_to_newer_dependencies = click.option(
     "-u",
     "--upgrade-to-newer-dependencies",
@@ -319,6 +327,7 @@ option_version_suffix_for_pypi_ci = click.option(
 @option_tag_as_latest
 @option_upgrade_on_failure
 @option_upgrade_to_newer_dependencies
+@option_use_uv_ci
 @option_verbose
 @option_version_suffix_for_pypi_ci
 def build(
@@ -359,6 +368,7 @@ def build(
     tag_as_latest: bool,
     upgrade_on_failure: bool,
     upgrade_to_newer_dependencies: bool,
+    use_uv: bool,
     version_suffix_for_pypi: str,
 ):
     """Build CI image. Include building multiple images for all python 
versions."""
@@ -425,6 +435,7 @@ def build(
         tag_as_latest=tag_as_latest,
         upgrade_on_failure=upgrade_on_failure,
         upgrade_to_newer_dependencies=upgrade_to_newer_dependencies,
+        use_uv=use_uv,
         version_suffix_for_pypi=version_suffix_for_pypi,
     )
     if platform:
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 97f5f23538..46d0a93a64 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
@@ -59,6 +59,7 @@ CI_IMAGE_TOOLS_PARAMETERS: dict[str, list[dict[str, str | 
list[str]]]] = {
                 "--debian-version",
                 "--install-mysql-client-type",
                 "--python-image",
+                "--use-uv",
             ],
         },
         {
diff --git 
a/dev/breeze/src/airflow_breeze/commands/production_image_commands_config.py 
b/dev/breeze/src/airflow_breeze/commands/production_image_commands_config.py
index 78b4b9e00d..df780dbbd8 100644
--- a/dev/breeze/src/airflow_breeze/commands/production_image_commands_config.py
+++ b/dev/breeze/src/airflow_breeze/commands/production_image_commands_config.py
@@ -53,10 +53,10 @@ PRODUCTION_IMAGE_TOOLS_PARAMETERS: dict[str, list[dict[str, 
str | list[str]]]] =
         {
             "name": "Advanced build options (for power users)",
             "options": [
+                "--additional-pip-install-flags",
+                "--commit-sha",
                 "--debian-version",
                 "--python-image",
-                "--commit-sha",
-                "--additional-pip-install-flags",
             ],
         },
         {
diff --git 
a/dev/breeze/src/airflow_breeze/commands/release_management_commands.py 
b/dev/breeze/src/airflow_breeze/commands/release_management_commands.py
index 68a21549f9..b1d53d7065 100644
--- a/dev/breeze/src/airflow_breeze/commands/release_management_commands.py
+++ b/dev/breeze/src/airflow_breeze/commands/release_management_commands.py
@@ -214,6 +214,8 @@ class VersionedFile(NamedTuple):
 
 
 AIRFLOW_PIP_VERSION = "24.0"
+AIRFLOW_UV_VERSION = "0.1.10"
+AIRFLOW_USE_UV = False
 WHEEL_VERSION = "0.36.2"
 GITPYTHON_VERSION = "3.1.40"
 RICH_VERSION = "13.7.0"
diff --git a/dev/breeze/src/airflow_breeze/params/build_ci_params.py 
b/dev/breeze/src/airflow_breeze/params/build_ci_params.py
index 2883eded8c..be412e3c14 100644
--- a/dev/breeze/src/airflow_breeze/params/build_ci_params.py
+++ b/dev/breeze/src/airflow_breeze/params/build_ci_params.py
@@ -41,6 +41,7 @@ class BuildCiParams(CommonBuildParams):
     eager_upgrade_additional_requirements: str | None = None
     skip_provider_dependencies_check: bool = False
     skip_image_upgrade_check: bool = False
+    use_uv: bool = True
     warn_image_upgrade_needed: bool = False
 
     @property
@@ -65,6 +66,7 @@ class BuildCiParams(CommonBuildParams):
         self._req_arg("AIRFLOW_IMAGE_DATE_CREATED", 
self.airflow_image_date_created)
         self._req_arg("AIRFLOW_IMAGE_REPOSITORY", 
self.airflow_image_repository)
         self._req_arg("AIRFLOW_PRE_CACHED_PIP_PACKAGES", 
self.airflow_pre_cached_pip_packages)
+        self._req_arg("AIRFLOW_USE_UV", self.use_uv)
         self._req_arg("AIRFLOW_VERSION", self.airflow_version)
         self._req_arg("BUILD_ID", self.build_id)
         self._req_arg("CONSTRAINTS_GITHUB_REPOSITORY", 
self.constraints_github_repository)
diff --git a/dev/breeze/src/airflow_breeze/params/build_prod_params.py 
b/dev/breeze/src/airflow_breeze/params/build_prod_params.py
index 334edaab48..342ac0c143 100644
--- a/dev/breeze/src/airflow_breeze/params/build_prod_params.py
+++ b/dev/breeze/src/airflow_breeze/params/build_prod_params.py
@@ -51,10 +51,10 @@ class BuildProdParams(CommonBuildParams):
     install_airflow_reference: str | None = None
     install_airflow_version: str | None = None
     install_packages_from_context: bool = False
-    use_constraints_for_context_packages: bool = False
     installation_method: str = "."
     runtime_apt_command: str | None = None
     runtime_apt_deps: str | None = None
+    use_constraints_for_context_packages: bool = False
 
     @property
     def airflow_version(self) -> str:
diff --git a/docs/docker-stack/build-arg-ref.rst 
b/docs/docker-stack/build-arg-ref.rst
index a3dbf447ad..805dc1dfe6 100644
--- a/docs/docker-stack/build-arg-ref.rst
+++ b/docs/docker-stack/build-arg-ref.rst
@@ -47,6 +47,10 @@ Those are the most common arguments that you use when you 
want to build a custom
 
+------------------------------------------+------------------------------------------+---------------------------------------------+
 | ``AIRFLOW_PIP_VERSION``                  | ``24.0``                          
       |  PIP version used.                          |
 
+------------------------------------------+------------------------------------------+---------------------------------------------+
+| ``AIRFLOW_UV_VERSION``                   | ``0.1.10``                        
       |  UV version used.                           |
++------------------------------------------+------------------------------------------+---------------------------------------------+
+| ``AIRFLOW_USE_UV``                       | ``false``                         
       |  Whether to use UV.                         |
++------------------------------------------+------------------------------------------+---------------------------------------------+
 | ``ADDITIONAL_PIP_INSTALL_FLAGS``         |                                   
       | additional ``pip`` flags passed to the      |
 |                                          |                                   
       | installation commands (except when          |
 |                                          |                                   
       | reinstalling ``pip`` itself)                |
diff --git a/scripts/docker/common.sh b/scripts/docker/common.sh
index 0e04f2f4bd..3aca611440 100644
--- a/scripts/docker/common.sh
+++ b/scripts/docker/common.sh
@@ -18,6 +18,10 @@
 # shellcheck shell=bash
 set -euo pipefail
 
+: "${AIRFLOW_PIP_VERSION:?Should be set}"
+: "${AIRFLOW_UV_VERSION:?Should be set}"
+: "${AIRFLOW_USE_UV:?Should be set}"
+
 function common::get_colors() {
     COLOR_BLUE=$'\e[34m'
     COLOR_GREEN=$'\e[32m'
@@ -31,6 +35,40 @@ function common::get_colors() {
     export COLOR_YELLOW
 }
 
+function common::get_packaging_tool() {
+    ## IMPORTANT: IF YOU MODIFY THIS FUNCTION YOU SHOULD ALSO MODIFY 
CORRESPONDING FUNCTION IN
+    ## `scripts/in_container/_in_container_utils.sh`
+    if [[ ${AIRFLOW_USE_UV} == "true" ]]; then
+        echo
+        echo "${COLOR_BLUE}Using 'uv' to install Airflow${COLOR_RESET}"
+        echo
+        export PACKAGING_TOOL="uv"
+        export PACKAGING_TOOL_CMD="uv pip"
+        export EXTRA_INSTALL_FLAGS=""
+        export EXTRA_UNINSTALL_FLAGS=""
+        export RESOLUTION_HIGHEST_FLAG="--resolution highest"
+        export RESOLUTION_LOWEST_DIRECT_FLAG="--resolution lowest-direct"
+        # We need to lie about VIRTUAL_ENV to make uv works
+        # Until https://github.com/astral-sh/uv/issues/1396 is fixed
+        # In case we are running user installation, we need to set VIRTUAL_ENV 
to user's home + .local
+        if [[ ${PIP_USER=} == "true" ]]; then
+            VIRTUAL_ENV="${HOME}/.local"
+        else
+            VIRTUAL_ENV=$(python -c "import sys; print(sys.prefix)")
+        fi
+        export VIRTUAL_ENV
+    else
+        echo
+        echo "${COLOR_BLUE}Using 'pip' to install Airflow${COLOR_RESET}"
+        echo
+        export PACKAGING_TOOL="pip"
+        export PACKAGING_TOOL_CMD="pip"
+        export EXTRA_INSTALL_FLAGS="--root-user-action ignore"
+        export EXTRA_UNINSTALL_FLAGS="--yes"
+        export RESOLUTION_HIGHEST_FLAG="--upgrade-strategy eager"
+        export RESOLUTION_LOWEST_DIRECT_FLAG="--upgrade --upgrade-strategy 
only-if-needed"
+    fi
+}
 
 function common::get_airflow_version_specification() {
     if [[ -z ${AIRFLOW_VERSION_SPECIFICATION=}
@@ -66,20 +104,41 @@ function common::get_constraints_location() {
     fi
 }
 
-function common::show_pip_version_and_location() {
+function common::show_packaging_tool_version_and_location() {
    echo "PATH=${PATH}"
-   echo "pip on path: $(which pip)"
-   echo "Using pip: $(pip --version)"
+   if [[ ${PACKAGING_TOOL} == "pip" ]]; then
+       echo "${COLOR_BLUE}Using 'pip' to install Airflow${COLOR_RESET}"
+       echo "pip on path: $(which pip)"
+       echo "Using pip: $(pip --version)"
+   else
+       echo "${COLOR_BLUE}Using 'uv' to install Airflow${COLOR_RESET}"
+       echo "uv on path: $(which uv)"
+       echo "Using uv: $(uv --version)"
+   fi
 }
 
-function common::install_pip_version() {
+function common::install_packaging_tool() {
     echo
     echo "${COLOR_BLUE}Installing pip version 
${AIRFLOW_PIP_VERSION}${COLOR_RESET}"
     echo
     if [[ ${AIRFLOW_PIP_VERSION} =~ .*https.* ]]; then
-        pip install --disable-pip-version-check "pip @ ${AIRFLOW_PIP_VERSION}"
+        # shellcheck disable=SC2086
+        pip install --root-user-action ignore --disable-pip-version-check "pip 
@ ${AIRFLOW_PIP_VERSION}"
     else
-        pip install --disable-pip-version-check "pip==${AIRFLOW_PIP_VERSION}"
+        # shellcheck disable=SC2086
+        pip install --root-user-action ignore --disable-pip-version-check 
"pip==${AIRFLOW_PIP_VERSION}"
+    fi
+    if [[ ${AIRFLOW_USE_UV} == "true" ]]; then
+        echo
+        echo "${COLOR_BLUE}Installing uv version 
${AIRFLOW_UV_VERSION}${COLOR_RESET}"
+        echo
+        if [[ ${AIRFLOW_UV_VERSION} =~ .*https.* ]]; then
+            # shellcheck disable=SC2086
+            pip install --root-user-action ignore --disable-pip-version-check 
"uv @ ${AIRFLOW_UV_VERSION}"
+        else
+            # shellcheck disable=SC2086
+            pip install --root-user-action ignore --disable-pip-version-check 
"uv==${AIRFLOW_UV_VERSION}"
+        fi
     fi
     mkdir -p "${HOME}/.local/bin"
 }
diff --git a/scripts/docker/entrypoint_ci.sh b/scripts/docker/entrypoint_ci.sh
index 875d0b2291..4e770992bc 100755
--- a/scripts/docker/entrypoint_ci.sh
+++ b/scripts/docker/entrypoint_ci.sh
@@ -221,8 +221,12 @@ function check_boto_upgrade() {
     echo
     echo "${COLOR_BLUE}Upgrading boto3, botocore to latest version to run 
Amazon tests with them${COLOR_RESET}"
     echo
-    pip uninstall --root-user-action ignore aiobotocore s3fs -y || true
-    pip install --root-user-action ignore --upgrade boto3 botocore
+    # shellcheck disable=SC2086
+    ${PACKAGING_TOOL_CMD} uninstall ${EXTRA_UNINSTALL_FLAGS} aiobotocore s3fs 
|| true
+    # We need to include oss2 as dependency as otherwise jmespath will be 
bumped and it will not pass
+    # the pip check test, Similarly gcloud-aio-auth limit is needed to be 
included as it bumps cryptography
+    # shellcheck disable=SC2086
+    ${PACKAGING_TOOL_CMD} install ${EXTRA_INSTALL_FLAGS} --upgrade boto3 
botocore "oss2>=2.14.0" "gcloud-aio-auth>=4.0.0,<5.0.0"
     pip check
 }
 
@@ -232,25 +236,31 @@ function check_pydantic() {
         echo
         echo "${COLOR_YELLOW}Reinstalling airflow from local sources to 
account for pyproject.toml changes${COLOR_RESET}"
         echo
-        pip install --root-user-action ignore -e .
+        # shellcheck disable=SC2086
+        ${PACKAGING_TOOL_CMD} install ${EXTRA_INSTALL_FLAGS} -e .
         echo
         echo "${COLOR_YELLOW}Remove pydantic and 3rd party libraries that 
depend on it${COLOR_RESET}"
         echo
-        pip uninstall --root-user-action ignore pydantic aws-sam-translator 
openai pyiceberg qdrant-client cfn-lint -y
+        # shellcheck disable=SC2086
+        ${PACKAGING_TOOL_CMD} uninstall ${EXTRA_UNINSTALL_FLAGS} pydantic 
aws-sam-translator openai \
+           pyiceberg qdrant-client cfn-lint weaviate-client
         pip check
     elif [[ ${PYDANTIC=} == "v1" ]]; then
         echo
         echo "${COLOR_YELLOW}Reinstalling airflow from local sources to 
account for pyproject.toml changes${COLOR_RESET}"
         echo
-        pip install --root-user-action ignore -e .
+        # shellcheck disable=SC2086
+        ${PACKAGING_TOOL_CMD} install ${EXTRA_INSTALL_FLAGS} -e .
         echo
-        echo "${COLOR_YELLOW}Uninstalling pyicberg which is not compatible 
with Pydantic 1${COLOR_RESET}"
+        echo "${COLOR_YELLOW}Uninstalling dependencies which are not 
compatible with Pydantic 1${COLOR_RESET}"
         echo
-        pip uninstall pyiceberg -y
+        # shellcheck disable=SC2086
+        ${PACKAGING_TOOL_CMD} uninstall ${EXTRA_UNINSTALL_FLAGS} pyiceberg 
waeviate-client
         echo
         echo "${COLOR_YELLOW}Downgrading Pydantic to < 2${COLOR_RESET}"
         echo
-        pip install --upgrade "pydantic<2.0.0"
+        # shellcheck disable=SC2086
+        ${PACKAGING_TOOL_CMD} install ${EXTRA_INSTALL_FLAGS} --upgrade 
"pydantic<2.0.0"
         pip check
     else
         echo
@@ -269,7 +279,8 @@ function check_download_sqlalchemy() {
     echo
     echo "${COLOR_BLUE}Downgrading sqlalchemy to minimum supported version: 
${min_sqlalchemy_version}${COLOR_RESET}"
     echo
-    pip install --root-user-action ignore 
"sqlalchemy==${min_sqlalchemy_version}"
+    # shellcheck disable=SC2086
+    ${PACKAGING_TOOL_CMD} install ${EXTRA_INSTALL_FLAGS} 
"sqlalchemy==${min_sqlalchemy_version}"
     pip check
 }
 
@@ -282,7 +293,8 @@ function check_download_pendulum() {
     echo
     echo "${COLOR_BLUE}Downgrading pendulum to minimum supported version: 
${min_pendulum_version}${COLOR_RESET}"
     echo
-    pip install --root-user-action ignore "pendulum==${min_pendulum_version}"
+    # shellcheck disable=SC2086
+    ${PACKAGING_TOOL_CMD} install ${EXTRA_INSTALL_FLAGS} 
"pendulum==${min_pendulum_version}"
     pip check
 }
 
diff --git a/scripts/docker/install_additional_dependencies.sh 
b/scripts/docker/install_additional_dependencies.sh
index 742e701f60..79e83eeb9e 100644
--- a/scripts/docker/install_additional_dependencies.sh
+++ b/scripts/docker/install_additional_dependencies.sh
@@ -20,7 +20,6 @@ set -euo pipefail
 
 : "${UPGRADE_TO_NEWER_DEPENDENCIES:?Should be true or false}"
 : "${ADDITIONAL_PYTHON_DEPS:?Should be set}"
-: "${AIRFLOW_PIP_VERSION:?Should be set}"
 
 # shellcheck source=scripts/docker/common.sh
 . "$( dirname "${BASH_SOURCE[0]}" )/common.sh"
@@ -32,10 +31,10 @@ function install_additional_dependencies() {
         echo "${COLOR_BLUE}Installing additional dependencies while upgrading 
to newer dependencies${COLOR_RESET}"
         echo
         set -x
-        pip install --root-user-action ignore --upgrade --upgrade-strategy 
eager \
+        ${PACKAGING_TOOL_CMD} install ${EXTRA_INSTALL_FLAGS} --upgrade 
${RESOLUTION_HIGHEST_FLAG} \
             ${ADDITIONAL_PIP_INSTALL_FLAGS} \
             ${ADDITIONAL_PYTHON_DEPS} ${EAGER_UPGRADE_ADDITIONAL_REQUIREMENTS=}
-        common::install_pip_version
+        common::install_packaging_tool
         set +x
         echo
         echo "${COLOR_BLUE}Running 'pip check'${COLOR_RESET}"
@@ -46,10 +45,10 @@ function install_additional_dependencies() {
         echo "${COLOR_BLUE}Installing additional dependencies upgrading only 
if needed${COLOR_RESET}"
         echo
         set -x
-        pip install --root-user-action ignore --upgrade --upgrade-strategy 
only-if-needed \
+        ${PACKAGING_TOOL_CMD} install ${EXTRA_INSTALL_FLAGS} --upgrade 
"${RESOLUTION_LOWEST_DIRECT_FLAG}" \
             ${ADDITIONAL_PIP_INSTALL_FLAGS} \
             ${ADDITIONAL_PYTHON_DEPS}
-        common::install_pip_version
+        common::install_packaging_tool
         set +x
         echo
         echo "${COLOR_BLUE}Running 'pip check'${COLOR_RESET}"
@@ -59,9 +58,10 @@ function install_additional_dependencies() {
 }
 
 common::get_colors
+common::get_packaging_tool
 common::get_airflow_version_specification
 common::override_pip_version_if_needed
 common::get_constraints_location
-common::show_pip_version_and_location
+common::show_packaging_tool_version_and_location
 
 install_additional_dependencies
diff --git a/scripts/docker/install_airflow.sh 
b/scripts/docker/install_airflow.sh
index dd70188239..e8fb806ac6 100644
--- a/scripts/docker/install_airflow.sh
+++ b/scripts/docker/install_airflow.sh
@@ -29,8 +29,6 @@
 # shellcheck source=scripts/docker/common.sh
 . "$( dirname "${BASH_SOURCE[0]}" )/common.sh"
 
-: "${AIRFLOW_PIP_VERSION:?Should be set}"
-
 function install_airflow() {
     # Coherence check for editable installation mode.
     if [[ ${AIRFLOW_INSTALLATION_METHOD} != "." && \
@@ -55,22 +53,21 @@ function install_airflow() {
         echo "${COLOR_BLUE}Remove airflow and all provider packages installed 
before potentially${COLOR_RESET}"
         echo
         set -x
-        pip freeze | grep apache-airflow | xargs pip uninstall --yes 
2>/dev/null || true
+        ${PACKAGING_TOOL_CMD} freeze | grep apache-airflow | xargs 
${PACKAGING_TOOL_CMD} uninstall ${EXTRA_UNINSTALL_FLAGS} 2>/dev/null || true
         set +x
         echo
         echo "${COLOR_BLUE}Installing all packages with eager upgrade with 
${AIRFLOW_INSTALL_EDITABLE_FLAG} mode${COLOR_RESET}"
         echo
         set -x
-        pip install --root-user-action ignore \
-            --upgrade --upgrade-strategy eager \
+        ${PACKAGING_TOOL_CMD} install ${EXTRA_INSTALL_FLAGS} --upgrade 
${RESOLUTION_HIGHEST_FLAG} \
             ${ADDITIONAL_PIP_INSTALL_FLAGS} \
             ${AIRFLOW_INSTALL_EDITABLE_FLAG} \
             
"${AIRFLOW_INSTALLATION_METHOD}[${AIRFLOW_EXTRAS}]${AIRFLOW_VERSION_SPECIFICATION}"
 \
             ${EAGER_UPGRADE_ADDITIONAL_REQUIREMENTS=}
         set +x
-        common::install_pip_version
+        common::install_packaging_tool
         echo
-        echo "${COLOR_BLUE}Running 'pip check'${COLOR_RESET}"
+        echo "${COLOR_BLUE}Running '${PACKAGING_TOOL} check'${COLOR_RESET}"
         echo
         pip check
     else
@@ -78,17 +75,17 @@ function install_airflow() {
         echo "${COLOR_BLUE}Installing all packages with constraints and 
upgrade if needed${COLOR_RESET}"
         echo
         set -x
-        pip install --root-user-action ignore ${AIRFLOW_INSTALL_EDITABLE_FLAG} 
\
+        ${PACKAGING_TOOL_CMD} install ${EXTRA_INSTALL_FLAGS} 
${AIRFLOW_INSTALL_EDITABLE_FLAG} \
             ${ADDITIONAL_PIP_INSTALL_FLAGS} \
             
"${AIRFLOW_INSTALLATION_METHOD}[${AIRFLOW_EXTRAS}]${AIRFLOW_VERSION_SPECIFICATION}"
 \
             --constraint "${AIRFLOW_CONSTRAINTS_LOCATION}" || true
-        common::install_pip_version
+        common::install_packaging_tool
         # then upgrade if needed without using constraints to account for new 
limits in pyproject.toml
-        pip install --root-user-action ignore --upgrade --upgrade-strategy 
only-if-needed \
+        ${PACKAGING_TOOL_CMD} install ${EXTRA_INSTALL_FLAGS} --upgrade 
${RESOLUTION_LOWEST_DIRECT_FLAG} \
             ${ADDITIONAL_PIP_INSTALL_FLAGS} \
             ${AIRFLOW_INSTALL_EDITABLE_FLAG} \
             
"${AIRFLOW_INSTALLATION_METHOD}[${AIRFLOW_EXTRAS}]${AIRFLOW_VERSION_SPECIFICATION}"
-        common::install_pip_version
+        common::install_packaging_tool
         set +x
         echo
         echo "${COLOR_BLUE}Running 'pip check'${COLOR_RESET}"
@@ -99,9 +96,10 @@ function install_airflow() {
 }
 
 common::get_colors
+common::get_packaging_tool
 common::get_airflow_version_specification
 common::override_pip_version_if_needed
 common::get_constraints_location
-common::show_pip_version_and_location
+common::show_packaging_tool_version_and_location
 
 install_airflow
diff --git a/scripts/docker/install_airflow_dependencies_from_branch_tip.sh 
b/scripts/docker/install_airflow_dependencies_from_branch_tip.sh
index f8bfabd1e2..202a40c41e 100644
--- a/scripts/docker/install_airflow_dependencies_from_branch_tip.sh
+++ b/scripts/docker/install_airflow_dependencies_from_branch_tip.sh
@@ -34,7 +34,6 @@
 : "${AIRFLOW_BRANCH:?Should be set}"
 : "${INSTALL_MYSQL_CLIENT:?Should be true or false}"
 : "${INSTALL_POSTGRES_CLIENT:?Should be true or false}"
-: "${AIRFLOW_PIP_VERSION:?Should be set}"
 
 function install_airflow_dependencies_from_branch_tip() {
     echo
@@ -50,25 +49,26 @@ function install_airflow_dependencies_from_branch_tip() {
     # dependencies that we can cache and reuse when installing airflow using 
constraints and latest
     # pyproject.toml in the next step (when we install regular airflow).
     set -x
-    pip install --root-user-action ignore \
+    ${PACKAGING_TOOL_CMD} install ${EXTRA_INSTALL_FLAGS} \
       ${ADDITIONAL_PIP_INSTALL_FLAGS} \
       "apache-airflow[${AIRFLOW_EXTRAS}] @ 
https://github.com/${AIRFLOW_REPO}/archive/${AIRFLOW_BRANCH}.tar.gz";
-    common::install_pip_version
+    common::install_packaging_tool
     # Uninstall airflow and providers to keep only the dependencies. In the 
future when
     # planned https://github.com/pypa/pip/issues/11440 is implemented in pip 
we might be able to use this
     # flag and skip the remove step.
-    pip freeze | grep apache-airflow-providers | xargs pip uninstall --yes 
2>/dev/null || true
+    ${PACKAGING_TOOL_CMD} freeze | grep apache-airflow-providers | xargs 
${PACKAGING_TOOL_CMD} uninstall ${EXTRA_UNINSTALL_FLAGS} 2>/dev/null || true
     set +x
     echo
     echo "${COLOR_BLUE}Uninstalling just airflow. Dependencies remain. Now 
target airflow can be reinstalled using mostly cached 
dependencies${COLOR_RESET}"
     echo
-    pip uninstall --yes apache-airflow || true
+    ${PACKAGING_TOOL_CMD} uninstall ${EXTRA_UNINSTALL_FLAGS} apache-airflow || 
true
 }
 
 common::get_colors
+common::get_packaging_tool
 common::get_airflow_version_specification
 common::override_pip_version_if_needed
 common::get_constraints_location
-common::show_pip_version_and_location
+common::show_packaging_tool_version_and_location
 
 install_airflow_dependencies_from_branch_tip
diff --git a/scripts/docker/install_from_docker_context_files.sh 
b/scripts/docker/install_from_docker_context_files.sh
index 8d9c6cdd85..6e286b9d9c 100644
--- a/scripts/docker/install_from_docker_context_files.sh
+++ b/scripts/docker/install_from_docker_context_files.sh
@@ -24,8 +24,6 @@
 # shellcheck source=scripts/docker/common.sh
 . "$( dirname "${BASH_SOURCE[0]}" )/common.sh"
 
-: "${AIRFLOW_PIP_VERSION:?Should be set}"
-
 function install_airflow_and_providers_from_docker_context_files(){
     if [[ ${INSTALL_MYSQL_CLIENT} != "true" ]]; then
         AIRFLOW_EXTRAS=${AIRFLOW_EXTRAS/mysql,}
@@ -42,7 +40,7 @@ function 
install_airflow_and_providers_from_docker_context_files(){
     fi
 
     # shellcheck disable=SC2206
-    local pip_flags=(
+    local packaging_flags=(
         # Don't quote this -- if it is empty we don't want it to create an
         # empty array element
         --find-links="file:///docker-context-files"
@@ -92,7 +90,7 @@ function 
install_airflow_and_providers_from_docker_context_files(){
             echo
             # force reinstall all airflow + provider packages with constraints 
found in
             set -x
-            pip install "${pip_flags[@]}" --root-user-action ignore --upgrade \
+            ${PACKAGING_TOOL_CMD} install ${EXTRA_INSTALL_FLAGS} 
"${packaging_flags[@]}" --upgrade \
                 ${ADDITIONAL_PIP_INSTALL_FLAGS} --constraint 
"${local_constraints_file}" \
                 ${reinstalling_apache_airflow_package} 
${reinstalling_apache_airflow_providers_packages}
             set +x
@@ -101,7 +99,7 @@ function 
install_airflow_and_providers_from_docker_context_files(){
             echo "${COLOR_BLUE}Installing docker-context-files packages with 
constraints from GitHub${COLOR_RESET}"
             echo
             set -x
-            pip install "${pip_flags[@]}" --root-user-action ignore \
+            ${PACKAGING_TOOL_CMD} install ${EXTRA_INSTALL_FLAGS} 
"${packaging_flags[@]}" \
                 ${ADDITIONAL_PIP_INSTALL_FLAGS} \
                 --constraint "${AIRFLOW_CONSTRAINTS_LOCATION}" \
                 ${reinstalling_apache_airflow_package} 
${reinstalling_apache_airflow_providers_packages}
@@ -112,12 +110,12 @@ function 
install_airflow_and_providers_from_docker_context_files(){
         echo "${COLOR_BLUE}Installing docker-context-files packages without 
constraints${COLOR_RESET}"
         echo
         set -x
-        pip install "${pip_flags[@]}" --root-user-action ignore \
+        ${PACKAGING_TOOL_CMD} install ${EXTRA_INSTALL_FLAGS} 
"${packaging_flags[@]}" \
             ${ADDITIONAL_PIP_INSTALL_FLAGS} \
             ${reinstalling_apache_airflow_package} 
${reinstalling_apache_airflow_providers_packages}
         set +x
     fi
-    common::install_pip_version
+    common::install_packaging_tool
     pip check
 }
 
@@ -126,7 +124,6 @@ function 
install_airflow_and_providers_from_docker_context_files(){
 # method on air-gaped system where you do not want to download any 
dependencies from remote hosts
 # which is a requirement for serious installations
 function install_all_other_packages_from_docker_context_files() {
-
     echo
     echo "${COLOR_BLUE}Force re-installing all other package from local files 
without dependencies${COLOR_RESET}"
     echo
@@ -136,20 +133,20 @@ function 
install_all_other_packages_from_docker_context_files() {
         grep -v apache_airflow | grep -v apache-airflow || true)
     if [[ -n "${reinstalling_other_packages}" ]]; then
         set -x
-        pip install ${ADDITIONAL_PIP_INSTALL_FLAGS} \
-            --root-user-action ignore --force-reinstall --no-deps --no-index 
${reinstalling_other_packages}
-        common::install_pip_version
+        ${PACKAGING_TOOL_CMD} install ${EXTRA_INSTALL_FLAGS} 
${ADDITIONAL_PIP_INSTALL_FLAGS} \
+            --force-reinstall --no-deps --no-index 
${reinstalling_other_packages}
+        common::install_packaging_tool
         set +x
     fi
 }
 
 common::get_colors
+common::get_packaging_tool
 common::get_airflow_version_specification
 common::override_pip_version_if_needed
 common::get_constraints_location
-common::show_pip_version_and_location
+common::show_packaging_tool_version_and_location
 
 install_airflow_and_providers_from_docker_context_files
 
-common::show_pip_version_and_location
 install_all_other_packages_from_docker_context_files
diff --git a/scripts/docker/install_mssql.sh b/scripts/docker/install_mssql.sh
index 9f33e56e8a..a174a1039b 100644
--- a/scripts/docker/install_mssql.sh
+++ b/scripts/docker/install_mssql.sh
@@ -24,7 +24,6 @@ set -euo pipefail
 common::get_colors
 declare -a packages
 
-: "${AIRFLOW_PIP_VERSION:?Should be set}"
 : "${INSTALL_MSSQL_CLIENT:?Should be true or false}"
 
 
diff --git a/scripts/docker/install_pip_version.sh 
b/scripts/docker/install_pip_version.sh
index afe46c7c1f..af8d25e06e 100644
--- a/scripts/docker/install_pip_version.sh
+++ b/scripts/docker/install_pip_version.sh
@@ -19,11 +19,10 @@
 # shellcheck source=scripts/docker/common.sh
 . "$( dirname "${BASH_SOURCE[0]}" )/common.sh"
 
-: "${AIRFLOW_PIP_VERSION:?Should be set}"
-
 common::get_colors
+common::get_packaging_tool
 common::get_airflow_version_specification
 common::override_pip_version_if_needed
-common::show_pip_version_and_location
+common::show_packaging_tool_version_and_location
 
-common::install_pip_version
+common::install_packaging_tool
diff --git a/scripts/docker/install_pipx_tools.sh 
b/scripts/docker/install_pipx_tools.sh
index 2207db994d..534dce8a93 100644
--- a/scripts/docker/install_pipx_tools.sh
+++ b/scripts/docker/install_pipx_tools.sh
@@ -24,7 +24,7 @@ function install_pipx_tools() {
     echo "${COLOR_BLUE}Installing pipx tools${COLOR_RESET}"
     echo
     # Make sure PIPX is installed in latest version
-    pip install --root-user-action ignore  --upgrade "pipx>=1.2.1"
+    ${PACKAGING_TOOL_CMD} install ${EXTRA_INSTALL_FLAGS} --upgrade 
"pipx>=1.2.1"
     if [[ $(uname -m) != "aarch64" ]]; then
         # Do not install mssql-cli for ARM
         # Install all the tools we need available in command line but without 
impacting the current environment
@@ -38,5 +38,6 @@ function install_pipx_tools() {
 }
 
 common::get_colors
+common::get_packaging_tool
 
 install_pipx_tools
diff --git a/scripts/in_container/_in_container_utils.sh 
b/scripts/in_container/_in_container_utils.sh
index b8d11c61e2..e991b1b413 100644
--- a/scripts/in_container/_in_container_utils.sh
+++ b/scripts/in_container/_in_container_utils.sh
@@ -60,8 +60,44 @@ function in_container_go_to_airflow_sources() {
     pushd "${AIRFLOW_SOURCES}" >/dev/null 2>&1 || exit 1
 }
 
+function in_container_get_packaging_tool() {
+    ## IMPORTANT: IF YOU MODIFY THIS FUNCTION YOU SHOULD ALSO MODIFY 
CORRESPONDING FUNCTION IN
+    ## `scripts/docker/common.sh`
+    if [[ ${AIRFLOW_USE_UV} == "true" ]]; then
+        echo
+        echo "${COLOR_BLUE}Using 'uv' to install Airflow${COLOR_RESET}"
+        echo
+        export PACKAGING_TOOL=""
+        export PACKAGING_TOOL_CMD="uv pip"
+        export EXTRA_INSTALL_FLAGS=""
+        export EXTRA_UNINSTALL_FLAGS=""
+        export RESOLUTION_HIGHEST_FLAG="--resolution highest"
+        export RESOLUTION_LOWEST_DIRECT_FLAG="--resolution lowest-direct"
+        # We need to lie about VIRTUAL_ENV to make uv works
+        # Until https://github.com/astral-sh/uv/issues/1396 is fixed
+        # In case we are running user installation, we need to set VIRTUAL_ENV 
to user's home + .local
+        if [[ ${PIP_USER=} == "true" ]]; then
+            VIRTUAL_ENV="${HOME}/.local"
+        else
+            VIRTUAL_ENV=$(python -c "import sys; print(sys.prefix)")
+        fi
+        export VIRTUAL_ENV
+    else
+        echo
+        echo "${COLOR_BLUE}Using 'pip' to install Airflow${COLOR_RESET}"
+        echo
+        export PACKAGING_TOOL="pip"
+        export PACKAGING_TOOL_CMD="pip"
+        export EXTRA_INSTALL_FLAGS="--root-user-action ignore"
+        export EXTRA_UNINSTALL_FLAGS="--yes"
+        export RESOLUTION_HIGHEST_FLAG="--upgrade-strategy eager"
+        export RESOLUTION_LOWEST_DIRECT_FLAG="--upgrade --upgrade-strategy 
only-if-needed"
+    fi
+}
+
 function in_container_basic_check() {
     assert_in_container
+    in_container_get_packaging_tool
     in_container_go_to_airflow_sources
 }
 
@@ -93,3 +129,4 @@ function in_container_set_colors() {
 
 export CI=${CI:="false"}
 export GITHUB_ACTIONS=${GITHUB_ACTIONS:="false"}
+export AIRFLOW_USE_UV=${AIRFLOW_USE_UV:="false"}
diff --git a/tests/cli/commands/test_webserver_command.py 
b/tests/cli/commands/test_webserver_command.py
index 2122c508f0..376e9e0097 100644
--- a/tests/cli/commands/test_webserver_command.py
+++ b/tests/cli/commands/test_webserver_command.py
@@ -230,7 +230,7 @@ class TestCLIGetNumReadyWorkersRunning:
 class TestCliWebServer(_ComonCLIGunicornTestClass):
     main_process_regexp = r"airflow webserver"
 
-    @pytest.mark.execution_timeout(210)
+    @pytest.mark.execution_timeout(400)
     def test_cli_webserver_background(self, tmp_path):
         with mock.patch.dict(
             "os.environ",
@@ -272,11 +272,14 @@ class TestCliWebServer(_ComonCLIGunicornTestClass):
                 assert self._find_process(r"airflow webserver", 
print_found_process=True)
                 console.print("[blue]Waiting for gunicorn processes:")
                 # wait for gunicorn to start
-                for i in range(30):
+                for i in range(120):
                     if self._find_process(r"^gunicorn"):
                         break
                     console.print("[blue]Waiting for gunicorn to start ...")
                     time.sleep(1)
+                else:
+                    console.print("[red]Gunicorn processes not found after 120 
seconds")
+                    assert False
                 console.print("[blue]Running gunicorn processes:")
                 assert self._find_all_processes("^gunicorn", 
print_found_process=True)
                 console.print("[magenta]Webserver process started 
successfully.")
diff --git a/tests/providers/weaviate/hooks/test_weaviate.py 
b/tests/providers/weaviate/hooks/test_weaviate.py
index 075b3816fa..650f938dba 100644
--- a/tests/providers/weaviate/hooks/test_weaviate.py
+++ b/tests/providers/weaviate/hooks/test_weaviate.py
@@ -23,11 +23,12 @@ from unittest.mock import MagicMock, Mock
 import pandas as pd
 import pytest
 import requests
-import weaviate
-from weaviate import ObjectAlreadyExistsException
 
-from airflow.models import Connection
-from airflow.providers.weaviate.hooks.weaviate import WeaviateHook
+weaviate = pytest.importorskip("weaviate")
+from weaviate import ObjectAlreadyExistsException  # noqa: E402
+
+from airflow.models import Connection  # noqa: E402
+from airflow.providers.weaviate.hooks.weaviate import WeaviateHook  # noqa: 
E402
 
 TEST_CONN_ID = "test_weaviate_conn"
 
diff --git a/tests/providers/weaviate/operators/test_weaviate.py 
b/tests/providers/weaviate/operators/test_weaviate.py
index b675cf5e64..597062a308 100644
--- a/tests/providers/weaviate/operators/test_weaviate.py
+++ b/tests/providers/weaviate/operators/test_weaviate.py
@@ -20,7 +20,9 @@ from unittest.mock import MagicMock, patch
 
 import pytest
 
-from airflow.providers.weaviate.operators.weaviate import (
+pytest.importorskip("weaviate")
+
+from airflow.providers.weaviate.operators.weaviate import (  # noqa: E402
     WeaviateDocumentIngestOperator,
     WeaviateIngestOperator,
 )


Reply via email to