feat(bookworm): take Node.js from the official image instead of apt - #518
Merged
Merged
Conversation
The full image is about to take its Node.js runtime from the official node image, and that image is not published for linux/386 -- no Node major ever was. Keeping 386 in the full image would mean carrying an apt fallback for that one platform, which would pin it to Debian's Node 18. The minimal image is built by a separate step and keeps all four platforms, including linux/386. Only the bundled-runtime image loses it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TQUFMNHUonVQShZWDP3vA5
Debian bookworm ships Node 18, which reached end-of-life in April 2025 and will never be updated there. Copying the runtime out of node:22-bookworm-slim gives Node 22 (supported until April 2027) and drops the "npm install -g npm@latest" step, because that image already carries a current npm. Only /usr/local/bin/node and /usr/local/lib/node_modules are copied. Copying all of /usr/local would break the image: the perl base image keeps perl, cpanm and cpm in the very same prefix. libatomic1 is installed explicitly. Node links against it on 32-bit arm, and where the deb used to pull it in as a package dependency, a COPY resolves no dependencies at all -- without it node dies on linux/arm/v7 with "libatomic.so.1: cannot open shared object file". Verified by building the copy against the perl base image for linux/arm64 and linux/arm/v7 (node v22.23.3, npm 10.9.9, perl intact on both) and by applying the new layer to the current full image, where perl, GD, DBD::SQLite, Inline::Python, Python 3.11 and pychromecast all keep working. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TQUFMNHUonVQShZWDP3vA5
CPAN Build Report✅ All CPAN builds are fine — 6 of 6 image/platform combinations verified successfully. Per image details
Full inventories, logs and per-image reports are available as workflow artifacts of the workflow run. |
From the fhem-core stage on, PERL5LIB points at XS modules that were built for the image perl in /usr/local (5.40). dpkg maintainer scripts run Debian's /usr/bin/perl (5.36) instead. Both share the same archname (arm-linux-gnueabihf-thread-multi-64int on the threaded arm/v7 image), so the system perl loads the image's Text::Iconv and aborts: Iconv.c: loadable library and perl binaries are mismatched In the extended layer this made x11-common's postinst fail on linux/arm/v7 threaded. That RUN only uses "set -x" without "-e", so the failure was swallowed: the published ghcr.io/fhem/fhem-docker:5-threaded-bookworm for arm/v7 ships x11-common half-configured and ffmpeg, libsdl2, libavdevice59, libice6, libaudio2 and mplayer unconfigured. The new nodejs layer runs with "set -e", its apt-get tries to finish those pending configurations, and the build now fails with exit code 100. PERL5LIB is unset inside the three RUN blocks that call apt after it is set (extended, python, nodejs). ENV is untouched, so FHEM still sees it at runtime. Reproduced on linux/arm/v7 with the threaded perl base image and the core lib from the published image: installing x11-common with PERL5LIB set exits 100 and leaves it half-configured; with PERL5LIB unset it exits 0, the package is installed and dpkg --audit is empty. On the published image, "dpkg --configure -a" without PERL5LIB repairs every pending package and ffmpeg runs again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TQUFMNHUonVQShZWDP3vA5
sidey79
added a commit
that referenced
this pull request
Sep 25, 2026
## Why `aptInstall()` runs `apt-get install` with `PERL5LIB` still pointing at the image's XS modules. On the published threaded image, Debian's system perl and the image perl share the same archname on `linux/386` and `linux/arm/v7`, so the system perl loads the image's `Text::Iconv` and the dpkg maintainer scripts abort: ``` Iconv.c: loadable library and perl binaries are mismatched dpkg: error processing package x11-common (--configure) ``` Reproduced against the published `ghcr.io/fhem/fhem-docker:5-threaded-bookworm`: | platform (threaded) | `aptInstall()` result | |---|---| | linux/amd64 | installs cleanly | | linux/arm64 | installs cleanly | | linux/arm/v7 | `exit=100`, half-configured | | linux/386 | `exit=100`, half-configured | `APT_PKGS` is already documented as deprecated. Rather than carrying the same `PERL5LIB` workaround used at build time (see #518) into a runtime code path for a feature nobody is meant to rely on anymore, `APT_PKGS` now only prints a notice and no longer calls `aptInstall()` or `apt-get` at all. ## What stays untouched `aptInstall()` itself is unchanged, including its existing bats unit tests. Nothing else calls it after this change, but removing the function is a separate decision from disconnecting `APT_PKGS` — this PR does only what was asked. ## Verification Sourced `entry.sh` with `APT_PKGS` set and `aptInstall()` replaced by a probe that fails if called: ``` INFO: APT_PKGS no longer installs packages in the running container. ERGEBNIS: initialPackageSetup lief durch, aptInstall wurde NICHT aufgerufen ``` `initialPackageSetup()` completes without invoking `aptInstall()`, and the notice is printed instead. `bash -n src/entry.sh` passes. ## Not covered `CPAN_PKGS`, `PIP_PKGS` and `NPM_PKGS` still perform their installs and are unaffected. `pip3 install` and `npm install -g` do not touch `PERL5LIB`-dependent dpkg maintainer scripts, so they are not known to share this bug. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01TQUFMNHUonVQShZWDP3vA5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The full image currently installs
nodejsfrom Debian bookworm, which means Node v18.20.4 — end-of-life since April 2025, and bookworm will never ship anything newer. Copying the runtime out ofnode:22-bookworm-slimgives Node 22, supported until April 2027, and keeps the version under Renovate's control like every other pinned image.The catch: platform coverage
No Node image is published for
linux/386, and from Node 24 onarm/v7is gone too:nodejsNode 22 therefore covers three of the four platforms the full image was built for. Rather than carrying an apt fallback for the one remaining platform — which would pin it to Node 18 anyway — the full image stops being built for
linux/386.The minimal image is unaffected and keeps all four platforms,
linux/386included. It is built by a separate step (target: with-fhem) and contains no Node.js at all.Two things that a COPY does not give you
/usr/localis not free real estate. The base image isperl:5.40.5-bookworm, and perl keeps its own installation there (/usr/local/bin/perl,/usr/local/lib/perl5, pluscpanmandcpm). A blanketCOPY --from=node /usr/local /usr/localwould wreck the perl runtime. Only/usr/local/bin/nodeand/usr/local/lib/node_modulesare copied, andnpm/npxare re-linked.A COPY resolves no dependencies. Node links against
libatomicon 32-bit arm. The deb used to pull that in automatically; without it,linux/arm/v7fails at runtime:libatomic1is now installed explicitly in that layer.Verification
Built the copy against the real perl base image per platform:
node=v22.23.3 npm=10.9.9 perl=5.040005node=v22.23.3 npm=10.9.9 perl=5.040005And applied the new layer to the current full image to check nothing else broke:
Inline::Pythonandpychromecastmatter here: Python stays on apt precisely becauseInline::Pythonis linked against Debian'slibpython3.11.so.1.0and the 41 C extensions indist-packagesare bound to that ABI. This PR does not touch the Python layer.Both Dockerfiles pass
docker buildx build --checkwith no warnings.What is not verified
No full multi-platform image was built end to end from here — that only happens in CI. The optional npm packages (
alexa-fhem,homebridge-fhem,gassistant-fhem,tradfri-fhem) are guarded byIMAGE_LAYER_NODEJS_EXT, which defaults to"0"and is never set by the workflow, so CI does not exercise them. Anyone building with that flag moves from Node 18 to Node 22 across two majors and should test those packages.Pre-existing bug surfaced by this PR: unconfigured packages on arm/v7 threaded
The first CI run failed on
linux/arm/v7(threaded) withexit code: 100in the nodejs layer — but not because oflibatomic1. apt tried to finish configuring packages left over from the extended layer:PERL5LIBpoints at XS modules built for the image perl 5.40 in/usr/local. dpkg maintainer scripts run Debian's/usr/bin/perl5.36, which on threaded arm/v7 has the same archname (arm-linux-gnueabihf-thread-multi-64int) and therefore loads the image'sText::Iconv. The extended layer only runsset -xwithout-e, so this has been silently swallowed. The currently published image is affected:ghcr.io/fhem/fhem-docker:5-threaded-bookwormx11-commonhalf-configuredinstalledThe fix unsets
PERL5LIBinside the threeRUNblocks that call apt after it is set (extended, python, nodejs).ENVstays as it is, so FHEM still gets it at runtime.Reproduced on
linux/arm/v7with the threaded perl base image plus the core lib from the published image:On the published image,
dpkg --configure -awithoutPERL5LIBrepairs every pending package andffmpegruns again.The same leak applies to packages installed at container start through the deprecated
APT_PKGSinentry.sh; that path is not changed here.Note
Only the bookworm variants are changed; the bullseye images are legacy.
🤖 Generated with Claude Code
https://claude.ai/code/session_01TQUFMNHUonVQShZWDP3vA5