Reduce size of Docker image
With some modifications to the Dockerfile I have achieved a 100MB size reduction in the resulting container image under my x86_64 test machine (264MB -> 165MB)
Changes done are:
- Used the
python:3.11-alpine3.19image as runtime container base - Replaced
gosuwithsu-execfrom Alpine Linux world - Cleaned pip and pipenv package cache from resulting runtime image
I've found it to work great under my setup so far, however have not tested the authentication methods thoroughly yet
Hey @Code-Otto, thanks for the contribution. I like the ideas here but unfortunately this PR doesn't currently build on linux/arm64. Are you able to look at this?
Looks like the cffi Python library (and probably many more) does not offers prebuilt musl-libc/arm64 binaries so pipenv tries to compile it from sources but since our container lacks a build environment and C compiler it fails
I'm facing this same problem trying to build for the armv7 architecture (wanna run Flatnotes in my home Raspberry Pi server) and got an idea on the works, which if functional should also fix arm64
I'll keep you updated
Got a solution fixing linux/arm64 builds and enabling linux/arm/v7, linux/arm/v6 and linux/i386 platform compatibility
I've moved the pipenv installation to a new separate build stage in the Dockerfile, from where the newly installed Python libraries are copied to the final runtime container This pipenv-build stage gets populated with C / C++ / Rust compilation tools and required libraries (As found to be required on a trial-and-error basis) so pipenv can compile sources whenever prebuilts are not available
As a nice side effect, the pipenv tool and its dependencies no longer make it into the runtime container saving around 35MB from the runtime container
This seems like a good approach. Do you think using python:3.11-slim-bullseye for the Pipenv build stage would negate the need to manually add dependencies? As this image isn't deployed there isn't a need to use Alpine.
That'd be nice but unfortunately since Alpine and Debian use different sets of libraries (IE musl v.s glibc libc) the binaries compiled in them cannot be guaranteed to run on each other That's why the Pipenv build stage image needs to match what's at the runtime image
Ah, that's a shame.
If I'm honest, I'm not keen on the additional complexity here and if it were just for the smaller image I wouldn't be keen. But enabling builds for other architectures would be great so I'll run some tests as soon as I get a chance.
Cheers for your efforts 👍🏻
Okay, that's understandable
We could turn back to using Debian images here to revert the new dependency maintenance burden, while keeping the main approach to make armv7/6 builds possible (And release container sizes would still be getting some reductions)
Also should this ultimately not merge feel free to take any changes you like from this PR and adapt them into a solution of your own
I haven't try but some python libraries could be installed with alpine's apk
https://pkgs.alpinelinux.org/package/v3.19/community/armv7/py3-josepy https://pkgs.alpinelinux.org/package/v3.19/main/armv7/py3-cffi
Been testing with a Debian container as a base and I'm afraid the statement from my previous comment is wrong: no matter Debian or Alpine our armv7/6 builds will require the build dependencies (libffi libffi-dev libssl3 openssl-dev) to be manually installed
I've summarized the current situtation here:
| Original Dockerfile/Debian | x86_64 | arm64 | armv7/6, i386 |
|---|---|---|---|
| Can build | :white_check_mark: | :white_check_mark: | :x: |
| PyPi prebuilts *[1] | :white_check_mark: | :white_check_mark: | - |
| Smallest size | :x: | :x: | - |
| New Dockerfile/Debian | x86_64 | arm64 | armv7/6, i386 |
|---|---|---|---|
| Can build | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| PyPi prebuilts *[1] | :white_check_mark: | :white_check_mark: | :x: |
| Smallest size | :x: | :x: | :x: |
| New Dockerfile/Alpine | x86_64 | arm64 | armv7/6, i386 |
|---|---|---|---|
| Can build | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| PyPi prebuilts *[1] | :white_check_mark: | :x: | :x: |
| Smallest size | :white_check_mark: | :white_check_mark: | :white_check_mark: |
[1] Prebuilt library binaries available from PyPi, no need for the build stage and manual dependency installation
Some of the possible ways forward I can think of:
-
No armv7/6 compatibility, no maintenance burden -> Keep the original Dockerfile as is and apply the changes regarding cleaning pip/pipenv cache, still would save ~20MB
-
armv7/6 compatibility, some maintenance burden -> Turn my Dockerfile solution back to Debian. If the pipenv build setup breaks x86_64 and arm64 builds will still work like usual because PyPi prebuilts are available and only other architectures builds would break. Higher savings from no pip/pipenv tools ending up at the release image
-
armv7/6 compatibility, smallest containers, highest maintenance burden -> My current Dockerfile solution as is. If the pipenv build setup breaks only x86_64 will still build correctly (PyPi prebuilts available). Highest savings
Also an hybrid solution: add my solution as a new 'experimental' Dockerfile for advanced users who want a more compact/raspberry-pi image and leave the original as the default, safe route
Looking forward for your thoughts on it. Hopefully I've not made this even more confusing with my verbosity :sweat_smile:
@qaqland thanks for the heads up, this could maybe open up an alternate route... (?)
Great job @Code-Otto, this is an awesome breakdown!
Although support for an older version of arm has been requested before I think it was a one-off and I don't expect the demand to be high (and will lessen as time progresses). Equally, a smaller container size is a noble goal but weighed up against additional complexity and maintenance, my preference is always for simplicity (hence the design ethos for flatnotes).
Your 'experimental' Dockerfile idea is good though and I'd be happy to include one. Maybe Dokerfile.experimental? I'm equally happy to include the cache savings as well.
Thanks again for your efforts.
Pushed a new commit moving the new Dockerfile to Dockerfile.experimental and restoring the previous Dockerfile with added pip/pipenv cache cleanup, as well as a entrypoint script tweak so it's compatible with the images produced by both them