Skip to content

Commit 53c0ac7

Browse files
committed
Add wheel-builder stage to pre-build Python dependencies
Introduce a multi-stage build approach where Python packages are compiled into wheels in a dedicated wheel-builder stage, then installed in the final image without requiring build dependencies. Benefits: - Build dependencies (gcc, python-devel, etc.) are no longer installed/removed in the final image stage - Improved build caching - wheel-builder layer is reused if package list doesn't change - Cleaner separation between compilation and runtime Signed-off-by: Riccardo Pittau <elfosardo@gmail.com>
1 parent a058212 commit 53c0ac7

File tree

3 files changed

+99
-41
lines changed

3 files changed

+99
-41
lines changed

Dockerfile

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,42 @@ RUN git clone https://github.com/ipxe/ipxe.git && \
2727
COPY prepare-efi.sh /bin/
2828
RUN prepare-efi.sh centos
2929

30+
## Build Python wheels for all dependencies
31+
FROM $BASE_IMAGE AS wheel-builder
32+
33+
# build arguments for source build customization
34+
ARG UPPER_CONSTRAINTS_FILE=upper-constraints.txt
35+
ARG IRONIC_SOURCE=7fe20fe31f0e184c68b7029e04acdb5d286fc4b2 # main
36+
ARG SUSHY_SOURCE
37+
38+
# Set environment variables for the build script
39+
ENV IRONIC_SOURCE=${IRONIC_SOURCE}
40+
ENV SUSHY_SOURCE=${SUSHY_SOURCE}
41+
ENV UPPER_CONSTRAINTS_FILE=${UPPER_CONSTRAINTS_FILE}
42+
43+
# Install build dependencies for compiling Python packages
44+
RUN --mount=type=cache,target=/var/cache/dnf \
45+
echo "install_weak_deps=False" >> /etc/dnf/dnf.conf && \
46+
echo "tsflags=nodocs" >> /etc/dnf/dnf.conf && \
47+
echo "keepcache=1" >> /etc/dnf/dnf.conf && \
48+
dnf install -y \
49+
gcc \
50+
git-core \
51+
python3.12-devel \
52+
python3.12-pip \
53+
python3.12-setuptools
54+
55+
# Install wheel and pin pip/setuptools versions
56+
RUN python3.12 -m pip install --no-cache-dir pip==24.1 setuptools==74.1.2 wheel
57+
58+
# Copy sources and package lists
59+
COPY sources /sources/
60+
COPY ${UPPER_CONSTRAINTS_FILE} ironic-packages-list /tmp/
61+
COPY build-wheels.sh /bin/
62+
63+
# Build all wheels
64+
RUN /bin/build-wheels.sh
65+
3066
# build actual image
3167
FROM $BASE_IMAGE
3268

@@ -43,21 +79,16 @@ ARG PKGS_LIST=main-packages-list.txt
4379
ARG EXTRA_PKGS_LIST
4480
ARG PATCH_LIST
4581

46-
# build arguments for source build customization
47-
ARG UPPER_CONSTRAINTS_FILE=upper-constraints.txt
48-
ARG IRONIC_SOURCE=7fe20fe31f0e184c68b7029e04acdb5d286fc4b2 # master
49-
ARG SUSHY_SOURCE
50-
51-
COPY sources /sources/
52-
COPY ${UPPER_CONSTRAINTS_FILE} ironic-packages-list ${PKGS_LIST} \
53-
${EXTRA_PKGS_LIST:-$PKGS_LIST} ${PATCH_LIST:-$PKGS_LIST} \
54-
/tmp/
82+
COPY ${PKGS_LIST} ${EXTRA_PKGS_LIST:-$PKGS_LIST} ${PATCH_LIST:-$PKGS_LIST} /tmp/
5583
COPY ironic-config/inspector.ipxe.j2 ironic-config/httpd-ironic-api.conf.j2 \
5684
ironic-config/ipxe_config.template ironic-config/dnsmasq.conf.j2 \
5785
/templates/
5886
COPY prepare-image.sh patch-image.sh configure-nonroot.sh /bin/
5987
COPY scripts/ /bin/
6088

89+
# Copy pre-built wheels from wheel-builder stage
90+
COPY --from=wheel-builder /wheels /wheels
91+
6192
RUN --mount=type=cache,target=/var/cache/dnf \
6293
prepare-image.sh && \
6394
rm -f /bin/prepare-image.sh

build-wheels.sh

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#!/usr/bin/bash
2+
3+
set -euxo pipefail
4+
5+
# This script builds Python wheels for all Ironic dependencies.
6+
# It runs in the wheel-builder stage of the Docker build.
7+
8+
UPPER_CONSTRAINTS_PATH="/tmp/${UPPER_CONSTRAINTS_FILE:-}"
9+
10+
# If the content of the upper-constraints file is empty,
11+
# we assume we're on the master branch
12+
if [[ ! -s "${UPPER_CONSTRAINTS_PATH}" ]]; then
13+
UPPER_CONSTRAINTS_PATH="/tmp/upper-constraints.txt"
14+
curl -L https://releases.openstack.org/constraints/upper/master -o "${UPPER_CONSTRAINTS_PATH}"
15+
fi
16+
17+
# Install jinja2 to render the package list template
18+
python3.12 -m pip install --no-cache-dir jinja2
19+
20+
IRONIC_PKG_LIST="/tmp/ironic-packages-list"
21+
IRONIC_PKG_LIST_FINAL="/tmp/ironic-packages-list-final"
22+
23+
# Render the Jinja2 template for the package list
24+
python3.12 -c 'import os; import sys; import jinja2; sys.stdout.write(jinja2.Template(sys.stdin.read()).render(env=os.environ, path=os.path))' < "${IRONIC_PKG_LIST}" > "${IRONIC_PKG_LIST_FINAL}"
25+
26+
# Remove sushy constraint if building from source
27+
if [[ -n ${SUSHY_SOURCE:-} ]]; then
28+
sed -i '/^sushy===/d' "${UPPER_CONSTRAINTS_PATH}"
29+
fi
30+
31+
# Build wheels for all packages
32+
# Note: some packages may not produce wheels (pure Python), but pip wheel handles this
33+
python3.12 -m pip wheel \
34+
--wheel-dir=/wheels \
35+
--no-cache-dir \
36+
-r "${IRONIC_PKG_LIST_FINAL}" \
37+
-c "${UPPER_CONSTRAINTS_PATH}"
38+
39+
# Also build wheels for runtime dependencies not in the main list
40+
python3.12 -m pip wheel \
41+
--wheel-dir=/wheels \
42+
--no-cache-dir \
43+
jinja2 watchdog \
44+
-c "${UPPER_CONSTRAINTS_PATH}"
45+
46+
echo "Wheels built successfully in /wheels"
47+
ls -la /wheels/

prepare-image.sh

Lines changed: 12 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -12,55 +12,35 @@ EOF
1212
IRONIC_UID=997
1313
IRONIC_GID=994
1414

15-
declare -a BUILD_DEPS=(
16-
gcc
17-
git-core
18-
python3.12-devel
19-
python3.12-setuptools
20-
)
21-
2215
dnf upgrade -y
2316

2417
# NOTE(dtantsur): pip is a requirement of python3 in CentOS
25-
dnf install -y python3.12-pip "${BUILD_DEPS[@]}"
18+
dnf install -y python3.12-pip
2619

2720
# NOTE(elfosardo): pinning pip and setuptools version to avoid
2821
# incompatibilities and errors during packages installation;
2922
# versions should be updated regularly, for example
3023
# after cutting a release branch.
3124
python3.12 -m pip install --no-cache-dir pip==24.1 setuptools==74.1.2
3225

33-
UPPER_CONSTRAINTS_PATH="/tmp/${UPPER_CONSTRAINTS_FILE:-}"
34-
35-
# NOTE(elfosardo): if the content of the upper-constraints file is empty,
36-
# we give as assumed that we're on the master branch
37-
if [[ ! -s "${UPPER_CONSTRAINTS_PATH}" ]]; then
38-
UPPER_CONSTRAINTS_PATH="/tmp/upper-constraints.txt"
39-
curl -L https://releases.openstack.org/constraints/upper/master -o "${UPPER_CONSTRAINTS_PATH}"
40-
fi
41-
42-
# NOTE(elfosardo): install dependencies constrained
43-
python3.12 -m pip install jinja2 watchdog -c "${UPPER_CONSTRAINTS_PATH}"
44-
45-
IRONIC_PKG_LIST="/tmp/ironic-packages-list"
46-
IRONIC_PKG_LIST_FINAL="/tmp/ironic-packages-list-final"
26+
# Install from pre-built wheels (built in wheel-builder stage)
27+
# No compilation needed here - wheels are already built
28+
python3.12 -m pip install \
29+
--no-cache-dir \
30+
--no-index \
31+
--find-links=/wheels \
32+
--ignore-installed \
33+
--prefix /usr \
34+
/wheels/*.whl
4735

48-
python3.12 -c 'import os; import sys; import jinja2; sys.stdout.write(jinja2.Template(sys.stdin.read()).render(env=os.environ, path=os.path))' < "${IRONIC_PKG_LIST}" > "${IRONIC_PKG_LIST_FINAL}"
49-
50-
if [[ -n ${SUSHY_SOURCE:-} ]]; then
51-
sed -i '/^sushy===/d' "${UPPER_CONSTRAINTS_PATH}"
52-
fi
53-
54-
python3.12 -m pip install --no-cache-dir --ignore-installed --prefix /usr -r "${IRONIC_PKG_LIST_FINAL}" -c "${UPPER_CONSTRAINTS_PATH}"
36+
# Clean up wheels after installation
37+
rm -rf /wheels
5538

5639
# ironic system configuration
5740
mkdir -p /var/log/ironic /var/lib/ironic
5841
getent group ironic > /dev/null || groupadd -r ironic -g "${IRONIC_GID}"
5942
getent passwd ironic > /dev/null || useradd -r -g ironic -u "${IRONIC_UID}" -s /sbin/nologin ironic -d /var/lib/ironic
6043

61-
# clean installed build dependencies
62-
dnf remove -y "${BUILD_DEPS[@]}"
63-
6444
xargs -rtd'\n' dnf install -y < /tmp/"${PKGS_LIST}"
6545

6646
if [[ -n "${EXTRA_PKGS_LIST:-}" ]]; then

0 commit comments

Comments
 (0)