44# - SecretsUsedInArgOrEnv : OPA_AUTH_MANAGER is a false positive and breaks the build.
55
66ARG GIT_SYNC_VERSION
7+ ARG UV_VERSION
78
89# For updated versions check https://github.com/kubernetes/git-sync/releases
910# which should contain a image location (e.g. registry.k8s.io/git-sync/git-sync:v3.6.8)
1011FROM oci.stackable.tech/sdp/git-sync/git-sync:${GIT_SYNC_VERSION} AS gitsync-image
1112
13+ FROM oci.stackable.tech/stackable/astral-sh/uv:${UV_VERSION} AS uv-image
14+
1215FROM local-image/shared/statsd-exporter AS statsd_exporter-builder
1316
1417FROM local-image/vector AS opa-auth-manager-builder
1518
1619ARG OPA_AUTH_MANAGER
1720ARG PYTHON_VERSION
18- ARG UV_VERSION
21+ ARG STACKABLE_USER_UID
1922
2023COPY airflow/opa-auth-manager/${OPA_AUTH_MANAGER} /tmp/opa-auth-manager
2124
2225WORKDIR /tmp/opa-auth-manager
2326
24- RUN <<EOF
25- microdnf update
26- microdnf install python${PYTHON_VERSION}-pip
27- microdnf clean all
27+ COPY --from=uv-image --chown=${STACKABLE_USER_UID}:0 /uv /uvx /bin/
2828
29- pip${PYTHON_VERSION} install --no-cache-dir uv==${UV_VERSION}
29+ RUN <<EOF
3030
3131# This folder is required by the tests to set up an sqlite database
3232mkdir /root/airflow
@@ -46,7 +46,6 @@ ARG STACKABLE_USER_UID
4646ARG NODEJS_VERSION
4747ARG S3FS_VERSION
4848ARG CYCLONEDX_BOM_VERSION
49- ARG UV_VERSION
5049
5150# Airflow "extras" packages are listed here: https://airflow.apache.org/docs/apache-airflow/stable/extra-packages-ref.html
5251# They evolve over time and thus belong to the version-specific arguments.
@@ -62,6 +61,8 @@ ARG AIRFLOW_EXTRAS_EXTERNAL_SERVICES=""
6261ARG AIRFLOW_EXTRAS_LOCALLY_INSTALLED_SOFTWARE=""
6362ARG AIRFLOW_EXTRAS_OTHER=""
6463
64+ COPY --from=uv-image --chown=${STACKABLE_USER_UID}:0 /uv /uvx /bin/
65+
6566RUN microdnf module enable -y nodejs:${NODEJS_VERSION} && \
6667 microdnf update && \
6768 microdnf install \
@@ -78,10 +79,6 @@ RUN microdnf module enable -y nodejs:${NODEJS_VERSION} && \
7879 libpq-devel \
7980 openldap-devel \
8081 openssl-devel \
81- python${PYTHON_VERSION} \
82- python${PYTHON_VERSION}-devel \
83- python${PYTHON_VERSION}-pip \
84- python${PYTHON_VERSION}-wheel \
8582 # The airflow odbc provider can compile without the development files (headers and libraries) (see https://github.com/stackabletech/docker-images/pull/683)
8683 unixODBC \
8784 # Needed for Airflow UI assets
@@ -103,23 +100,28 @@ COPY --chown=${STACKABLE_USER_UID}:0 airflow/stackable/patches/${PRODUCT_VERSION
103100
104101WORKDIR /stackable
105102
106- RUN <<EOF
103+ # Set a cache directory so we can mount the cache for layers that use it.
104+ ENV UV_CACHE_DIR=/var/cache/uv
105+ # Don't use links because they won't work after `COPY --from` in the final stage.
106+ ENV UV_LINK_MODE=copy
107+ # Store the uv-managed Python inside /stackable/ so it gets included in the
108+ # COPY --from=airflow-build-image step. Without this, the venv symlinks would
109+ # point to /usr/local/share/uv/python/... which is not copied to the final image.
110+ ENV UV_PYTHON_INSTALL_DIR=/stackable/python
111+
112+ RUN uv venv --python "${PYTHON_VERSION}" --system-site-packages /stackable/app
113+
114+ # Instead of activating the environment each time
115+ ENV VIRTUAL_ENV=/stackable/app
116+
117+ RUN --mount=type=cache,target=/var/cache/uv <<EOF
107118
108119# Compose comma-delimited AIRFLOW_EXTRAS
109120AIRFLOW_EXTRAS="$AIRFLOW_EXTRAS_CORE,$AIRFLOW_EXTRAS_META,$AIRFLOW_EXTRAS_PROVIDER_APACHE,$AIRFLOW_EXTRAS_EXTERNAL_SERVICES,$AIRFLOW_EXTRAS_LOCALLY_INSTALLED_SOFTWARE,$AIRFLOW_EXTRAS_OTHER"
110121
111122# Removing duplicates
112123AIRFLOW_EXTRAS=$(echo "$AIRFLOW_EXTRAS" | tr ',' '\n ' | awk 'NF > 0 {if (!seen[$0]++) print $0}' | tr '\n ' ',' | sed 's/,$//' )
113124
114- python${PYTHON_VERSION} -m venv --system-site-packages /stackable/app
115-
116- source /stackable/app/bin/activate
117-
118- # Upgrade pip to the latest version
119- # Also install uv to get support for build constraints
120- pip install --no-cache-dir --upgrade pip
121- pip install --no-cache-dir uv==${UV_VERSION}
122-
123125# Pin virtualenv due to a breaking change in 21.0.0 which is pulled
124126# in by hatch.
125127# See https://github.com/pypa/hatch/issues/2193
@@ -134,6 +136,7 @@ if [ -d "./airflow-core" ]; then
134136 cd airflow-core/src/airflow/ui
135137
136138 # build front-end assets
139+ # TODO: Consider making the pnpm version an ARG
137140 npm install -g pnpm@10.18.2
138141 pnpm install --frozen-lockfile
139142 pnpm run build
@@ -143,13 +146,14 @@ if [ -d "./airflow-core" ]; then
143146 cd ../../..
144147 /root/.local/bin/hatch build -t wheel
145148 # First install the full apache-airflow package to get all dependencies including database drivers
146- uv pip install --no-cache-dir apache-airflow[${AIRFLOW_EXTRAS}]==${PRODUCT_VERSION} --constraint /tmp/constraints.txt --build-constraints /tmp/build-constraints.txt
149+ uv pip install --python ${VIRTUAL_ENV}/bin/python apache-airflow[${AIRFLOW_EXTRAS}]==${PRODUCT_VERSION} --constraint /tmp/constraints.txt --build-constraints /tmp/build-constraints.txt
147150 # Then install the locally built core wheel to override the core package
148- uv pip install --no-cache-dir dist/apache_airflow_core-${PRODUCT_VERSION}-py3-none-any.whl[${AIRFLOW_EXTRAS}] --constraint /tmp/constraints.txt --build-constraints /tmp/build-constraints.txt
151+ uv pip install --python ${VIRTUAL_ENV}/bin/python dist/apache_airflow_core-${PRODUCT_VERSION}-py3-none-any.whl[${AIRFLOW_EXTRAS}] --constraint /tmp/constraints.txt --build-constraints /tmp/build-constraints.txt
149152else
150153 # Airflow 2.x
151154 # build front-end assets
152155 cd airflow/www
156+ # TODO: Consider making the yarn version an ARG
153157 npm install -g yarn@1.22.22
154158 yarn install --frozen-lockfile
155159 yarn run build
@@ -158,22 +162,22 @@ else
158162 cd ../..
159163 /root/.local/bin/hatch build -t wheel
160164 # First install the full apache-airflow package to get all dependencies including database drivers
161- uv pip install --no-cache-dir apache-airflow[${AIRFLOW_EXTRAS}]==${PRODUCT_VERSION} --constraint /tmp/constraints.txt --build-constraints /tmp/build-constraints.txt
165+ uv pip install --python ${VIRTUAL_ENV}/bin/python apache-airflow[${AIRFLOW_EXTRAS}]==${PRODUCT_VERSION} --constraint /tmp/constraints.txt --build-constraints /tmp/build-constraints.txt
162166 # Then install the locally built wheel to override with patched version
163- uv pip install --no-cache-dir dist/apache_airflow-${PRODUCT_VERSION}-py3-none-any.whl[${AIRFLOW_EXTRAS}] --constraint /tmp/constraints.txt --build-constraints /tmp/build-constraints.txt
167+ uv pip install --python ${VIRTUAL_ENV}/bin/python dist/apache_airflow-${PRODUCT_VERSION}-py3-none-any.whl[${AIRFLOW_EXTRAS}] --constraint /tmp/constraints.txt --build-constraints /tmp/build-constraints.txt
164168fi
165169
166170# Needed for pandas S3 integration to e.g. write and read csv and parquet files to/from S3
167171# TODO: s3fs may already be installed via the s3fs extra and constraints. Check if this explicit install is still needed.
168- uv pip install --no-cache-dir s3fs==${S3FS_VERSION} cyclonedx-bom==${CYCLONEDX_BOM_VERSION}
172+ uv pip install --python ${VIRTUAL_ENV}/bin/python s3fs==${S3FS_VERSION} cyclonedx-bom==${CYCLONEDX_BOM_VERSION}
169173# Needed for OIDC
170- uv pip install --no-cache-dir Flask_OIDC==2.2.0 Flask-OpenID==1.3.1
174+ uv pip install --python ${VIRTUAL_ENV}/bin/python Flask_OIDC==2.2.0 Flask-OpenID==1.3.1
171175
172- uv pip install --no-cache-dir /tmp/opa_auth_manager-0.1.0-py3-none-any.whl
176+ uv pip install --python ${VIRTUAL_ENV}/bin/python /tmp/opa_auth_manager-0.1.0-py3-none-any.whl
173177
174178# Create the SBOM for Airflow
175179# Important: All `pip install` commands must be above this line, otherwise the SBOM will be incomplete
176- cyclonedx-py environment --spec-version 1.5 --output-file /tmp/sbom.json
180+ ${VIRTUAL_ENV}/bin/ cyclonedx-py environment --spec-version 1.5 --output-file /tmp/sbom.json
177181uv pip uninstall cyclonedx-bom
178182
179183# Break circular dependencies by removing the apache-airflow dependency from the providers
@@ -215,8 +219,9 @@ LABEL name="Apache Airflow" \
215219 description="This image is deployed by the Stackable Operator for Apache Airflow."
216220
217221ENV HOME=/stackable
218- ENV AIRFLOW_USER_HOME_DIR=/stackable
219- ENV PATH=$PATH:/bin:$HOME/app/bin
222+ ENV VIRTUAL_ENV=$HOME/app
223+ ENV AIRFLOW_USER_HOME_DIR=$HOME
224+ ENV PATH=$HOME/app/bin:$PATH:/bin
220225ENV AIRFLOW_HOME=$HOME/airflow
221226
222227COPY --from=airflow-build-image --chown=${STACKABLE_USER_UID}:0 /stackable/ ${HOME}/
@@ -230,14 +235,15 @@ COPY --from=gitsync-image --chown=${STACKABLE_USER_UID}:0 /git-sync ${HOME}/git-
230235
231236COPY airflow/licenses /licenses
232237
238+ COPY --from=uv-image --chown=${STACKABLE_USER_UID}:0 /uv /uvx /bin/
239+
233240# Update image and install needed packages
234241RUN <<EOF
235242microdnf update
236243
237244# git: Needed for the gitsync functionality
238245# openldap: Needed for authentication of clients against LDAP servers
239246# openssh-clients: We need the openssh libs for the gitsync functionality (the clone target could be e.g. git@github.com:org/repo.git)
240- # python: Airflow needs Python
241247microdnf install \
242248 ca-certificates \
243249 cyrus-sasl \
@@ -248,7 +254,6 @@ microdnf install \
248254 openssh-clients \
249255 openssl-libs \
250256 openssl-pkcs11 \
251- python${PYTHON_VERSION} \
252257 socat \
253258 unixODBC
254259microdnf clean all
0 commit comments