Split up library builds into individual builder stages to preserve layer cache - #343
Conversation
There was a problem hiding this comment.
It seems you haven't yet signed a CLA. Please do so here.
Once you do that we will be able to review and accept this pull request.
Thanks!
|
Please take a look at the requested changes, and use the Ready for review button when you are done, thanks 👍 |
sairon
left a comment
There was a problem hiding this comment.
Sorry, it's a shame that no one noticed your PR earlier, but I'm all in for making this change happen! The final image is indeed quite bloated (HA Core has currently 33 layers) and we should definitely optimize it. That's actually the reason why I checked this repo and noticed your PR, which is mostly what I wanted to implement as well, so thanks a lot for that!
Apart from the issue with the cache you already mentioned, there's not much else to change, so I think we can go with it then.
Also, I think that for reducing the layer count even further, we could do something like this as the last step, instead of COPYing the individual parts, something like this:
RUN \
--mount=from=pip-install-builder,source=/root/.local,target=/mnt/pip \
--mount=from=ssocr-builder,source=/opt/ssocr,target=/mnt/ssocr \
--mount=from=libcec-builder,source=/opt/libcec,target=/mnt/libcec \
--mount=from=picotts-builder,source=/opt/picotts,target=/mnt/picotts \
--mount=from=telldus-builder,source=/opt/telldus,target=/mnt/telldus \
mkdir -p /root/.local \
&& cp -r /mnt/pip/* /root/.local/ \
&& cp -r /mnt/ssocr/* /usr/local/ \
&& cp -r /mnt/libcec/* /usr/local/ \
&& cp -r /mnt/picotts/* /usr/local/ \
&& cp -r /mnt/telldus/* /usr/local/ \
&& python_version=$(python -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')") \
&& echo "cec" > "/usr/local/lib/python${python_version}/site-packages/cec.pth"Indeed, you won't leverage the --link feature then, but IMO, since it happens by the end of the build, there's no disadvantage of cache invalidation, and the copy commands are not that much time consuming. Or the builds can be combined in another FROM scratch as merged-libs builder and then copied to the final image with COPY --link --from=merged-libs / /. What do you think?
@sairon I think the pros/cons here come down to implementation details later on in the build pipeline. Its been a while since I looked at all this 😆... but I think I remember that the main build pipeline re-triggers this base image build on each release? If so, I think reducing layers and/or dropping the Anyways, that is how I was thinking all this could work. Its hard to say for sure, there are a lot of moving parts downstream of this repo. If you still want to reduce the image layers and/or remove |
|
Oh, and just another note, besides validating the build works, I have no idea how to actually test these changes. For example, how to validate |
Being nice in theory, I don't think this would work given how the images are built now. The BuildKit cache is not preserved between builds by the builder currently, so the layer hashes will be different between each run of the base images' build. In such case it makes more sense to me to squash it to a single layer to reduce the number of layers the resulting HA image has. But we don't necessarily need to do it here, for now the PR is good as is. I'll do a full local build of Core based on this image and then ask at least for another pair of eyes to have a look at this. |
Yeah, I totally agree, it will require followup work on the downstream repo. The way I've done this elsewhere with buildx is to use From a pure docker layer efficiency point-of-view, it might be prudent to ask: Does it make sense to continue supporting a 'base image'? If caching is optimized enough, would it be acceptable to have the full image requirements built out in a single Dockerfile? From a local-dev point-of-view, I can see how having a base image is useful, but if cache is working well, maybe it wouldn't make a big difference? It would also allow for more streamlined development if someone needs to make changes to these "base image" dependencies - all being in the same Dockerfile. From a release image point of view - since these base images are always re-building, I don't think there is any benefit keeping them separate. I would be happy to help with PRs if this sounds like a good option. |
There hasn't been much maintenance on the base image, I am not even sure if these libraries are used. That said, I think your PR does things cleaner, and is definitely a step forward in case we want/need to keep some of these dependencies, so I am happy to merge this.
It probably makes sense to fold this into the |
|
I can't believe this got merged in after so many months! 🥳 |
With #343 the image ended up without git installed. This does not seem an intentional change, readd git so it is present in the base image.
It seems that Home Assistant needs the packages to be in /usr/local/lib and not in the user specific /root/.local directory. This partially reverts #343.
|
👋 Hey folks! This change seems to have broken the Pico TTS integration. There's an issue in the Seems like adding homeassistant:/config# strings $(which pico2wave) | grep picotts
/opt/picotts/lib
/opt/picotts/share/pico/lang/Is the change to building in # Copy from picotts builder
COPY --link --from=picotts-builder --exclude=/usr/src/pico /usr/ /usr/I dunno, I'm just guessing there. |
|
I'm going to try and put together a PR to fix this tonight. |
Why
Hello! I've noticed the docker image is quite hefty, and I was curious if I could improve it a bit. After taking a look, I realized I couldn't help much with reducing the image size 😆 .... but I thought it might be possible to improve layer reuse between builds. In the end, this feature branch is only 7.98mb smaller then whats on
master, but I believe layer reuse is now a possibility depending on how the builds and caching are set up.If no one thinks this PR provides any value, that’s no problem! It does introduce a bit more complexity, so I totally understand. Anyway, on to the changes I made:
What
I've moved each major build phase into its own builder stage using multi-stage builds:
ssocr,pip,libcec,PicoTTS, andTelldus. The results of those builder stages are then copied out into the 'main' stage. All temporary files were already being pruned nicely, so again, no real space savings. However, using theCOPY --linkcommand from the builder stages enables this cool docker feature:So, if you need to bump a version in
requirements.txt, usingCOPY --linkwill allow those other layer - likessocr- to remain unchanged. Pretty cool! If this PR works as expected, I hope that the next time I rundocker compose pull, it will require fewer layers to be pulled.Now... there is a bit of a gotcha with all this. This caching logic only works if the builds are correctly set up with caching. For example,
docker-composebuilds cannot create a multi-stage build cache. Looking around, I seebuildxis being used over athome-assistant/builder/, but there was a lot of logic going on, and I couldn't quite follow it all.So, there's a chance some follow-up changes might be needed before the benefits of this PR can be realized - for example, using
cache-toand ensuringmode=maxis set to enable the mutli-stage build cache. But one step at a time - if you all think this is an improvement worth making, we can iterate from here.Testing
For testing, I ran the build and verified that it runs. However, that doesn’t fully confirm that the libraries I modified are still being installed correctly. Some follow-up work is definitely required to verify everything is functioning as expected.