From cb97d9f3e3ad139dbd3eaea7f2dea1ca2d9135aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20marti?= Date: Sat, 25 Jul 2026 09:53:40 +0200 Subject: [PATCH] Split base image into runtime and builder stages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a 'runtime' target without build-essential/ccache (~250 MB smaller) for use as the final image in multi-stage ESPHome builds. The 'base' target retains build-essential and is used during pip install to compile native Python C extensions. Similarly, 'ha-addon-runtime' provides the HA add-on base without build tools, while 'ha-addon' keeps them for the build stage. PlatformIO does not need the host GCC — it downloads its own cross-compilers (xtensa, arm-none-eabi) at compile time. Tested locally: ESP32 firmware compiles successfully in the slim image. --- .github/workflows/build.yml | 55 ++++++++++++++++++++++++++++++++++++- debian/Dockerfile | 41 ++++++++++++++++++++++++--- 2 files changed, 91 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4153014..1cb292b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -66,6 +66,24 @@ jobs: fi echo version="$version" >> $GITHUB_OUTPUT + - name: Build runtime image + id: build-runtime + uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 + continue-on-error: ${{ github.event_name == 'pull_request' }} + env: + DOCKER_BUILD_SUMMARY: false + DOCKER_BUILD_RECORD_UPLOAD: false + with: + context: ${{ matrix.base }} + file: ./${{ matrix.base }}/Dockerfile + platforms: linux/amd64,linux/arm64 + target: runtime + cache-from: type=registry,ref=${{ steps.image.outputs.image }}:cache-${{ matrix.base }} + cache-to: ${{ github.event_name == 'release' && format('type=registry,ref={0}:cache-{1},mode=max', steps.image.outputs.image, matrix.base) || '' }} + push: true + tags: | + ${{ steps.image.outputs.image }}:${{ matrix.base }}-runtime-${{ steps.get_tag.outputs.version }} + - name: Build base image id: build-base uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 @@ -85,6 +103,24 @@ jobs: tags: | ${{ steps.image.outputs.image }}:${{ matrix.base }}-${{ steps.get_tag.outputs.version }} + - name: Build ha-addon-runtime image + id: build-ha-addon-runtime + uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 + continue-on-error: ${{ github.event_name == 'pull_request' }} + env: + DOCKER_BUILD_SUMMARY: false + DOCKER_BUILD_RECORD_UPLOAD: false + with: + context: ${{ matrix.base }} + file: ./${{ matrix.base }}/Dockerfile + platforms: linux/amd64,linux/arm64 + target: ha-addon-runtime + cache-from: type=registry,ref=${{ steps.image.outputs.image }}:cache-${{ matrix.base }} + cache-to: ${{ github.event_name == 'release' && format('type=registry,ref={0}:cache-{1},mode=max', steps.image.outputs.image, matrix.base) || '' }} + push: true + tags: | + ${{ steps.image.outputs.image }}:${{ matrix.base }}-ha-addon-runtime-${{ steps.get_tag.outputs.version }} + - name: Build ha-addon image id: build-ha-addon uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 @@ -114,6 +150,15 @@ jobs: -f ./${{ matrix.base }}/Dockerfile \ ./${{ matrix.base }} + - name: Build runtime image (local) + if: github.event_name == 'pull_request' && matrix.base == 'debian' + run: | + docker build \ + --target runtime \ + -t "ghcr.io/esphome/docker-base:${{ matrix.base }}-runtime-pr-${{ github.event.pull_request.number }}" \ + -f ./${{ matrix.base }}/Dockerfile \ + ./${{ matrix.base }} + - name: Build ha-addon image (local) if: github.event_name == 'pull_request' && matrix.base == 'debian' run: | @@ -123,6 +168,15 @@ jobs: -f ./${{ matrix.base }}/Dockerfile \ ./${{ matrix.base }} + - name: Build ha-addon-runtime image (local) + if: github.event_name == 'pull_request' && matrix.base == 'debian' + run: | + docker build \ + --target ha-addon-runtime \ + -t "ghcr.io/esphome/docker-base:${{ matrix.base }}-ha-addon-runtime-pr-${{ github.event.pull_request.number }}" \ + -f ./${{ matrix.base }}/Dockerfile \ + ./${{ matrix.base }} + - name: Clone ESPHome repository if: github.event_name == 'pull_request' && matrix.base == 'debian' uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -138,7 +192,6 @@ jobs: --target final \ --build-arg BUILD_TYPE=docker \ --build-arg BUILD_VERSION=ci \ - --build-arg "BUILD_OS=${{ matrix.base }}" \ --build-arg "BUILD_BASE_VERSION=pr-${{ github.event.pull_request.number }}" \ -t esphome-test:latest \ -f ./esphome/docker/Dockerfile \ diff --git a/debian/Dockerfile b/debian/Dockerfile index 175127b..50774b3 100644 --- a/debian/Dockerfile +++ b/debian/Dockerfile @@ -1,7 +1,8 @@ -FROM python:3.14-slim-trixie AS base +FROM python:3.14-slim-trixie AS runtime SHELL ["/bin/bash", "-o", "pipefail", "-c"] +# Runtime-only packages (no compiler toolchain) RUN \ set -x \ && apt update \ @@ -13,9 +14,7 @@ RUN \ libcairo2=1.18.4-1+b1 \ libmagic1t64=1:5.46-5 \ patch=2.8-2 \ - build-essential=12.12 \ libusb-1.0-0=2:1.0.28-1 \ - ccache=4.11.2-2 \ && apt purge -y --auto-remove \ && rm -rf \ /tmp/* \ @@ -29,7 +28,24 @@ ENV UV_SYSTEM_PYTHON=true ENV PIP_ROOT_USER_ACTION=ignore -FROM base AS ha-addon +# Builder stage: runtime + build-essential for compiling native Python extensions +FROM runtime AS base + +RUN \ + set -x \ + && apt update \ + && apt install -y --no-install-recommends \ + build-essential=12.12 \ + ccache=4.11.2-2 \ + && apt purge -y --auto-remove \ + && rm -rf \ + /tmp/* \ + /var/{cache,log}/* \ + /var/lib/apt/lists/* \ + /usr/src/* + + +FROM runtime AS ha-addon-runtime RUN \ set -x \ @@ -90,3 +106,20 @@ RUN \ WORKDIR /root ENTRYPOINT ["/init"] + + +# ha-addon with build tools (for pip install of native extensions) +FROM ha-addon-runtime AS ha-addon + +RUN \ + set -x \ + && apt update \ + && apt install -y --no-install-recommends \ + build-essential=12.12 \ + ccache=4.11.2-2 \ + && apt purge -y --auto-remove \ + && rm -rf \ + /tmp/* \ + /var/{cache,log}/* \ + /var/lib/apt/lists/* \ + /usr/src/*