diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..7165622 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,17 @@ +# Root cause of the node-pty "Cannot find module .../pty.node" failure (Phase 2A): with no +# .dockerignore, a host-side `npm install` (e.g. for local testing) leaves backend/node_modules +# on disk, built against the host's own Node/libnode — not the container's. The Dockerfile's +# later `COPY backend/ ./backend/` (done after the npm-install RUN layer, for build caching) +# then silently overwrites the correctly-built-in-container node_modules with that host copy, +# which fails to load at runtime with an ABI/architecture mismatch. Native modules (node-pty, +# better-sqlite3) MUST always be built inside the image, never leaked in from the host. +# +# IMPORTANT: bare `node_modules/` only matches a node_modules directory at the build context +# ROOT in Docker's .dockerignore matching rules — it does NOT recursively match +# backend/node_modules or frontend/node_modules. This bit us once already (fixed here with +# `**/`): a first pass at this file used the bare pattern and the leak still happened. +**/node_modules/ +frontend/dist/ +.git/ +*.db +*.db-journal diff --git a/Dockerfile b/Dockerfile index 67f4f53..b47fc9a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,6 +22,12 @@ RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ && apt-get install -y nodejs \ && rm -rf /var/lib/apt/lists/* +# node-gyp fix (Phase 2A root cause, see the note on the backend npm-install line below): +# set as a persistent image ENV, not a one-off RUN prefix, so it applies no matter when or how +# many times a native module gets (re)built in this image — inline `VAR=x npm install` only +# scopes the var to that one RUN command, which isn't a strong enough guarantee. +ENV npm_config_node_shared=false + # ── k3s (bundles kubectl, containerd, everything) ──────────────────────────── # We download the binary and the airgap images so the cluster starts offline. RUN set -eux && \ @@ -41,8 +47,23 @@ RUN set -eux && \ WORKDIR /app # Install backend dependencies +# +# npm_config_node_shared=false (set above, image-level) works around a real node-gyp bug +# affecting native modules (node-pty, better-sqlite3): node-gyp generates build/config.gypi +# from a Node headers/config reference it downloads for the target version, and for this +# NodeSource-packaged Node build that reference incorrectly reports node_shared=true (a shared +# libnode.so build) when the actual installed Node is statically linked (no libnode.so. +# exists at all). Left uncorrected, node-gyp links native addons against -lnode, producing a +# binary that fails at require() time with "libnode.so.: cannot open shared object file" +# — this is the prior node-pty runtime failure ("Cannot find module .../pty.node"), +# root-caused during Phase 2A: node-pty's own error handling collapses the real dlopen error +# into a generic "module not found" message across all the paths it checks, which is what made +# this look like a missing-file problem rather than a link-time misconfiguration. The +# `node -e require(...)` check below makes this a hard build failure if it ever regresses, +# instead of a silent runtime failure discovered later. COPY backend/package.json ./backend/ -RUN cd backend && npm install --production +RUN cd backend && npm install --production \ + && node -e "require('./node_modules/node-pty'); console.log('node-pty loads OK')" # Build frontend COPY frontend/package.json frontend/vite.config.js ./frontend/