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,29 @@ 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+ # This installs the Python runtime
113+ RUN uv venv --python "${PYTHON_VERSION}" --system-site-packages /stackable/app
114+
115+ # Instead of activating the environment each time
116+ ENV VIRTUAL_ENV=/stackable/app
117+
118+ RUN --mount=type=cache,target=/var/cache/uv <<EOF
107119
108120# Compose comma-delimited AIRFLOW_EXTRAS
109121AIRFLOW_EXTRAS="$AIRFLOW_EXTRAS_CORE,$AIRFLOW_EXTRAS_META,$AIRFLOW_EXTRAS_PROVIDER_APACHE,$AIRFLOW_EXTRAS_EXTERNAL_SERVICES,$AIRFLOW_EXTRAS_LOCALLY_INSTALLED_SOFTWARE,$AIRFLOW_EXTRAS_OTHER"
110122
111123# Removing duplicates
112124AIRFLOW_EXTRAS=$(echo "$AIRFLOW_EXTRAS" | tr ',' '\n ' | awk 'NF > 0 {if (!seen[$0]++) print $0}' | tr '\n ' ',' | sed 's/,$//' )
113125
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-
123126# Pin virtualenv due to a breaking change in 21.0.0 which is pulled
124127# in by hatch.
125128# See https://github.com/pypa/hatch/issues/2193
@@ -134,6 +137,7 @@ if [ -d "./airflow-core" ]; then
134137 cd airflow-core/src/airflow/ui
135138
136139 # build front-end assets
140+ # TODO: Consider making the pnpm version an ARG
137141 npm install -g pnpm@10.18.2
138142 pnpm install --frozen-lockfile
139143 pnpm run build
@@ -143,13 +147,14 @@ if [ -d "./airflow-core" ]; then
143147 cd ../../..
144148 /root/.local/bin/hatch build -t wheel
145149 # 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
150+ uv pip install --python ${VIRTUAL_ENV}/bin/python apache-airflow[${AIRFLOW_EXTRAS}]==${PRODUCT_VERSION} --constraint /tmp/constraints.txt --build-constraints /tmp/build-constraints.txt
147151 # 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
152+ 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
149153else
150154 # Airflow 2.x
151155 # build front-end assets
152156 cd airflow/www
157+ # TODO: Consider making the yarn version an ARG
153158 npm install -g yarn@1.22.22
154159 yarn install --frozen-lockfile
155160 yarn run build
@@ -158,25 +163,22 @@ else
158163 cd ../..
159164 /root/.local/bin/hatch build -t wheel
160165 # 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
166+ uv pip install --python ${VIRTUAL_ENV}/bin/python apache-airflow[${AIRFLOW_EXTRAS}]==${PRODUCT_VERSION} --constraint /tmp/constraints.txt --build-constraints /tmp/build-constraints.txt
162167 # 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
168+ 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
164169fi
165170
166171# Needed for pandas S3 integration to e.g. write and read csv and parquet files to/from S3
167- uv pip install --no-cache-dir s3fs==${S3FS_VERSION} cyclonedx-bom==${CYCLONEDX_BOM_VERSION}
172+ # TODO: s3fs may already be installed via the s3fs extra and constraints. Check if this explicit install is still needed.
173+ uv pip install --python ${VIRTUAL_ENV}/bin/python s3fs==${S3FS_VERSION} cyclonedx-bom==${CYCLONEDX_BOM_VERSION}
168174# Needed for OIDC
169- uv pip install --no-cache-dir Flask_OIDC==2.2.0 Flask-OpenID==1.3.1
175+ uv pip install --python ${VIRTUAL_ENV}/bin/python Flask_OIDC==2.2.0 Flask-OpenID==1.3.1
170176
171- uv pip install --no-cache-dir /tmp/opa_auth_manager-0.1.0-py3-none-any.whl
177+ uv pip install --python ${VIRTUAL_ENV}/bin/python /tmp/opa_auth_manager-0.1.0-py3-none-any.whl
172178
173179# Create the SBOM for Airflow
174180# Important: All `pip install` commands must be above this line, otherwise the SBOM will be incomplete
175- if [ "$PRODUCT_VERSION" == "2.9.3" ] || [ "$PRODUCT_VERSION" == "3.0.6" ]; then
176- cyclonedx-py environment --schema-version 1.5 --outfile /tmp/sbom.json
177- else
178- cyclonedx-py environment --spec-version 1.5 --output-file /tmp/sbom.json
179- fi
181+ ${VIRTUAL_ENV}/bin/cyclonedx-py environment --spec-version 1.5 --output-file /tmp/sbom.json
180182uv pip uninstall cyclonedx-bom
181183
182184# Break circular dependencies by removing the apache-airflow dependency from the providers
@@ -218,8 +220,9 @@ LABEL name="Apache Airflow" \
218220 description="This image is deployed by the Stackable Operator for Apache Airflow."
219221
220222ENV HOME=/stackable
221- ENV AIRFLOW_USER_HOME_DIR=/stackable
222- ENV PATH=$PATH:/bin:$HOME/app/bin
223+ ENV VIRTUAL_ENV=$HOME/app
224+ ENV AIRFLOW_USER_HOME_DIR=$HOME
225+ ENV PATH=$HOME/app/bin:$PATH:/bin
223226ENV AIRFLOW_HOME=$HOME/airflow
224227
225228COPY --from=airflow-build-image --chown=${STACKABLE_USER_UID}:0 /stackable/ ${HOME}/
@@ -233,14 +236,15 @@ COPY --from=gitsync-image --chown=${STACKABLE_USER_UID}:0 /git-sync ${HOME}/git-
233236
234237COPY airflow/licenses /licenses
235238
239+ COPY --from=uv-image --chown=${STACKABLE_USER_UID}:0 /uv /uvx /bin/
240+
236241# Update image and install needed packages
237242RUN <<EOF
238243microdnf update
239244
240245# git: Needed for the gitsync functionality
241246# openldap: Needed for authentication of clients against LDAP servers
242247# openssh-clients: We need the openssh libs for the gitsync functionality (the clone target could be e.g. git@github.com:org/repo.git)
243- # python: Airflow needs Python
244248microdnf install \
245249 ca-certificates \
246250 cyrus-sasl \
@@ -251,7 +255,6 @@ microdnf install \
251255 openssh-clients \
252256 openssl-libs \
253257 openssl-pkcs11 \
254- python${PYTHON_VERSION} \
255258 socat \
256259 unixODBC
257260microdnf clean all
0 commit comments