standardize every Node-based Dockerfile in ~/ on FROM node:24-alpine AND #15

Closed
opened 2026-05-13 19:01:58 +00:00 by brendan · 0 comments
Owner

Originally filed: 2026-05-12 in ~/features.md, block #1.
Cross-project companion issues: brendan/authd, brendan/buchinese, brendan/dashcam, brendan/inventory, brendan/movement

2026-05-12 — fleet chore: standardize every Node-based Dockerfile in ~/ on FROM node:24-alpine AND apk add --no-cache … git so the build image can resolve npm git+https:// dependencies and so we stop tripping npm 10.9's EBADPLATFORM trap on nested platform-specific optional deps

Motivation. Over the last 24 hours four projects (authd, buchinese, nanodrop, inventory) shipped a sqlite-migrate adoption PR and four deploys went red on the same root cause: their node:*-alpine build image doesn't ship git, so npm ci couldn't resolve bchen-sqlite-migrate@git+https://gitea.bchen.dev/brendan/sqlite-migrate.git#v0.1.0 and printed a misleading "tarball … seems to be corrupted" warning before exiting with ENOENT spawn git. The fixes have been (or are being) applied one-by-one as bugs surface — authd PR #16 (git added, base already on 24), nanodrop PR #10 (bumped to 24 + git added), buchinese pending, inventory pending. dashcam and movement haven't tripped the bug yet because they don't currently consume any git+https:// dep, but they have the same latent vulnerability and will fire the next time any of them adopts sqlite-migrate or any other private-Gitea-URL dep.

Separately, the node:22-alpine base ships npm 10.9, which has a documented bug where nested @esbuild/* platform-specific optional deps (introduced by e.g. vitest 4.x) are misclassified as required and cause EBADPLATFORM for @esbuild/aix-ppc64 on linux/x64 builds. authd already documented this and bumped to node:24-alpine (npm 11.x); buchinese is currently tripping the bug for real.

Current state (snapshot 2026-05-12 after git fetch against origin/main for each project; verified via ~/<repo>/Dockerfile):

project base image apk install line has git? consumes git+https://?
authd node:24-alpine apk add --no-cache python3 make g++ git YES (post PR #16) YES (bchen-sqlite-migrate)
buchinese node:22-alpine apk add --no-cache python3 make g++ NO YES (bchen-sqlite-migrate)
inventory node:22-alpine apk add --no-cache python3 make g++ NO YES (bchen-sqlite-migrate)
nanodrop node:24-alpine apk add --no-cache python3 make g++ git YES (post PR #10) YES (bchen-sqlite-migrate)
dashcam node:22-alpine apk add --no-cache ffmpeg python3 make g++ NO NO
movement node:20-alpine apk add --no-cache python3 make g++ NO NO

Scope. Bring every project in the table to the canonical shape: FROM node:24-alpine plus git appended to its existing apk install line (preserving any project-specific extras like dashcam's ffmpeg). Six PRs, one per project. Four of them (buchinese, inventory, dashcam, movement) actually need changes; two (authd, nanodrop) already match the target — implementer should verify and skip-with-note rather than ship a no-op PR.

For each project requiring a change:

  1. Edit the Dockerfile (~/<repo>/Dockerfile:1 and the RUN apk add line — typically line 3 or 4). Two-line diff: bump base if it's not already node:24-alpine, append git to the apk install if it's not there. Preserve project-specific apk extras (dashcam: ffmpeg; future projects may add imagemagick, curl, etc.).
  2. Verify locally before push:
    • npm ci against a clean node_modules succeeds.
    • tsc --noEmit clean.
    • npm run build (if a build step exists) clean.
    • Project-specific test suite (npm test or vitest run) green.
      No expected behavior changes; the diff is build-environment-only, so test-count-delta should be zero.
  3. docker build . can't be exercised inside the sandbox (no docker daemon socket reachable, same caveat as the existing sqlite-migrate-adoption PRs). Deferred to the first prod deploy on the new image.
  4. Open the PR using the per-project worktree pattern (per ~/roles/_sub-claude-rules.md): branch chore/dockerfile-node24-and-git, single chore: commit, auto-merge via the Gitea sync-from-main precondition. Refactor pass: noop on a two-line diff. Security pass: zero in scope (build-time dependency change; the runtime image gains git but no application code uses child_process/spawn/exec to invoke it on user input, so the footgun is purely "if you already have RCE you also have git").

Dependency-compatibility audit (so implementer doesn't have to redo it per project): every project in the table runs the same general TypeScript/Node stack — better-sqlite3, fastify, tsx, vitest — and authd's PR #15/#16 confirmed all of them ship node-24 prebuilds (better-sqlite3@12.6.2, bcrypt@6.0.0, fastify@5.7.4, tsx@4.21.0, vitest@4.0.18). nanodrop PR #10 reconfirmed the same. movement is currently on node:20-alpine (oldest in the fleet) — its better-sqlite3 and other native deps may need to be bumped to node-24-compatible versions; if npm ci after the base bump fails on a native-dep prebuild, ABANDON for movement specifically and re-file as a movement-only sub-item so the dep bumps can be handled separately. The other four are guaranteed-compatible per the prior PRs.

Acceptance.

  • Every Node-based Dockerfile in ~/ reads FROM node:24-alpine AND its RUN apk add line includes git.
  • For each project, the next deploy CI run on main (re-run of latest, or fresh trivial commit) reaches success. No EBADPLATFORM, no "tarball … seems to be corrupted", no ENOENT spawn git.
  • npm ci resolution of any future git+https:// dep works fleet-wide without per-project Dockerfile patching.
  • No application behavior changes; each project's test suite green delta is 0.
  • Two pending bugs in ~/bugs.md (buchinese, inventory — both filed 2026-05-12) become moot once this chore lands; bug-fixer should ABANDON them with a note pointing at the chore's merge commits.

Future-projects rule. A standing rule has been added to ~/.claude/CLAUDE.md ("Node + Alpine Docker base images — node:24-alpine + git") so every new Node-based project the user (or any Claude session) bootstraps starts with the canonical Dockerfile shape. That rule lives next to the existing iOS-16px-font-size standing rule — same template (file a retrofit chore for current fleet, then add a standing rule to prevent regression for everything new).

Filer notes.

  • This chore exists primarily to stop re-discovering the same root cause one project at a time. If the implementer ships it before the buchinese + inventory bugs are picked up by the bug-fixer, those bugs convert to no-ops — file resolution notes against them.
  • Recommended PR ordering: buchinese first (currently red, highest user-visible value), inventory second (also red), dashcam third (not failing today but ships ffmpeg — verify the apk add line preservation), movement last (oldest base, riskiest dep audit). authd and nanodrop are no-ops.
> **Originally filed:** 2026-05-12 in ~/features.md, block #1. > **Cross-project companion issues:** brendan/authd, brendan/buchinese, brendan/dashcam, brendan/inventory, brendan/movement **2026-05-12 — fleet chore: standardize every Node-based `Dockerfile` in `~/` on `FROM node:24-alpine` AND `apk add --no-cache … git` so the build image can resolve `npm` `git+https://` dependencies and so we stop tripping npm 10.9's EBADPLATFORM trap on nested platform-specific optional deps** <!-- FULLY RESOLVED 2026-05-12 — all 6 fleet projects now align with the standing rule. Per-project status: - **authd** — already on `node:24-alpine` + `git` pre-chore (PR #16, merged earlier). No-op verified by implementer. - **nanodrop** — already on `node:24-alpine` + `git` pre-chore (PR #10, merged earlier). No-op verified by implementer. - **buchinese** — PR https://gitea.bchen.dev/brendan/buchinese/pulls/15 (merge_commit 034250f, feat commit e92a13f at 2026-05-12 13:40 PST). Shipped by an earlier implementer iteration / out-of-band before this 23:23 UTC tick picked up the chore item. Verified by `git log -- Dockerfile`. - **inventory** — PR https://gitea.bchen.dev/brendan/inventory/pulls/22 (merge_commit 1cb6c44, feat commit 3bb9f4f). Shipped by an earlier implementer iteration / out-of-band similarly. Verified by `git log -- Dockerfile origin/main`. - **dashcam** — PR https://gitea.bchen.dev/brendan/dashcam/pulls/4 (merge_commit c4f90bf021e6569a256888d210c1288688fe74bc). REVIEWED_SHA=087140e48dbacb9e591412ec647728e842997d09. Shipped by THIS implementer iteration (2026-05-12 23:23 UTC). 2-line diff (`FROM node:22-alpine` → `node:24-alpine`; appended `git` to `RUN apk add --no-cache ffmpeg python3 make g++`); `ffmpeg`/`ffprobe` preserved for clip-duration probing. Sub-Claude dispatch initially blocked on `tea pr create` token-scope mismatch (token has `write:repository,write:user`; tea CLI requires `read:issue`); role session opened the PR via raw Gitea API `POST /api/v1/repos/brendan/dashcam/pulls` with the same token (the API path doesn't require `read:issue`), then SHA-pinned merge via `POST /pulls/4/merge` with `Do=merge head_commit_id=087140e…`. Verifications green pre-merge: `npm ci` clean, `tsc --noEmit` clean, `npm run build` clean, 82/82 vitest specs green. Branch deleted post-merge; worktree cleaned via `git worktree remove --force`. - **movement** — PR https://gitea.bchen.dev/brendan/movement/pulls/17 (merge_commit 33a6702dbb36a6c589a006f42ffad303e0555653). REVIEWED_SHA=74a3a4fc083cbe65f8d30418ff1702ae46ca05ed. Shipped by THIS implementer iteration. 2-line diff (`FROM node:20-alpine` → `node:24-alpine`; appended `git` to `RUN apk add --no-cache python3 make g++`). Native-dep compatibility risk flagged in the chore spec ("movement is on node:20, riskiest dep audit") confirmed harmless: `better-sqlite3@^12.9.0` and `bcrypt@^6.0.0` ship node-24 prebuilds, `npm ci` on clean `node_modules` exit 0 (7.8s, 0 errors; 15 pre-existing audit findings unrelated). Verifications green: `npm run typecheck` clean, `npx vite build` (what the Dockerfile actually runs) clean, 194/194 vitest specs green. PR + merge via raw Gitea API SHA-pinned (same path as dashcam). Branch deleted post-merge; worktree cleaned. `npm run build` (which runs `tsc -p tsconfig.server.json && vite build`) fails with a pre-existing `rootDir` error on main — verified the failure reproduces on `~/movement` main, so it's NOT caused by this change; filed-as-out-of-scope for a future cleanup (the Dockerfile doesn't invoke `npm run build`, only `npx vite build`, so prod deploys are unaffected). ENV_VAR_GATE clean for both shipped PRs (zero new/renamed/removed env vars). SECURITY None — pure build-image dep change; runtime image gains `git` but no application code shells out to `git` in either project; build-time only. Lockfile-verified: N/A for both — no dep manifest (`package.json` / `package-lock.json`) was touched, so the lockfile-sync invariant from `_sub-claude-rules.md` doesn't apply. `docker build .` deferred to first prod deploy for both (no docker daemon reachable from sandbox — same caveat as the prior 4 sqlite-migrate-adoption PRs in this chore family). Two open bugs in `~/bugs.md` (buchinese, inventory — both filed 2026-05-12 against the missing-`git` failure mode) are now moot since both projects merged the fix earlier today. Bug-fixer should ABANDON-with-resolution-note on those, pointing at merge_commits 034250f (buchinese) and 1cb6c44 (inventory). The standing rule in `~/.claude/CLAUDE.md` ("Node + Alpine Docker base images — `node:24-alpine` + `git`") is the regression guard going forward. --> **Motivation.** Over the last 24 hours four projects (authd, buchinese, nanodrop, inventory) shipped a sqlite-migrate adoption PR and four deploys went red on the same root cause: their `node:*-alpine` build image doesn't ship `git`, so `npm ci` couldn't resolve `bchen-sqlite-migrate@git+https://gitea.bchen.dev/brendan/sqlite-migrate.git#v0.1.0` and printed a misleading "tarball … seems to be corrupted" warning before exiting with `ENOENT spawn git`. The fixes have been (or are being) applied one-by-one as bugs surface — authd PR #16 (`git` added, base already on 24), nanodrop PR #10 (bumped to 24 + `git` added), buchinese pending, inventory pending. dashcam and movement haven't tripped the bug yet because they don't currently consume any `git+https://` dep, but they have the same latent vulnerability and will fire the next time any of them adopts sqlite-migrate or any other private-Gitea-URL dep. Separately, the `node:22-alpine` base ships npm 10.9, which has a documented bug where nested `@esbuild/*` platform-specific optional deps (introduced by e.g. vitest 4.x) are misclassified as required and cause `EBADPLATFORM` for `@esbuild/aix-ppc64` on linux/x64 builds. authd already documented this and bumped to `node:24-alpine` (npm 11.x); buchinese is currently tripping the bug for real. **Current state** (snapshot 2026-05-12 after `git fetch` against `origin/main` for each project; verified via `~/<repo>/Dockerfile`): | project | base image | apk install line | has `git`? | consumes `git+https://`? | |---|---|---|---|---| | authd | `node:24-alpine` | `apk add --no-cache python3 make g++ git` | YES (post PR #16) | YES (bchen-sqlite-migrate) | | buchinese | `node:22-alpine` | `apk add --no-cache python3 make g++` | NO | YES (bchen-sqlite-migrate) | | inventory | `node:22-alpine` | `apk add --no-cache python3 make g++` | NO | YES (bchen-sqlite-migrate) | | nanodrop | `node:24-alpine` | `apk add --no-cache python3 make g++ git` | YES (post PR #10) | YES (bchen-sqlite-migrate) | | dashcam | `node:22-alpine` | `apk add --no-cache ffmpeg python3 make g++` | NO | NO | | movement | `node:20-alpine` | `apk add --no-cache python3 make g++` | NO | NO | **Scope.** Bring every project in the table to the canonical shape: `FROM node:24-alpine` plus `git` appended to its existing apk install line (preserving any project-specific extras like dashcam's `ffmpeg`). Six PRs, one per project. Four of them (buchinese, inventory, dashcam, movement) actually need changes; two (authd, nanodrop) already match the target — implementer should verify and skip-with-note rather than ship a no-op PR. For each project requiring a change: 1. Edit the Dockerfile (`~/<repo>/Dockerfile:1` and the `RUN apk add` line — typically line 3 or 4). Two-line diff: bump base if it's not already `node:24-alpine`, append `git` to the apk install if it's not there. Preserve project-specific apk extras (dashcam: `ffmpeg`; future projects may add `imagemagick`, `curl`, etc.). 2. Verify locally before push: - `npm ci` against a clean `node_modules` succeeds. - `tsc --noEmit` clean. - `npm run build` (if a build step exists) clean. - Project-specific test suite (`npm test` or `vitest run`) green. No expected behavior changes; the diff is build-environment-only, so test-count-delta should be zero. 3. `docker build .` can't be exercised inside the sandbox (no docker daemon socket reachable, same caveat as the existing sqlite-migrate-adoption PRs). Deferred to the first prod deploy on the new image. 4. **Open the PR using the per-project worktree pattern** (per `~/roles/_sub-claude-rules.md`): branch `chore/dockerfile-node24-and-git`, single `chore:` commit, auto-merge via the Gitea sync-from-main precondition. Refactor pass: noop on a two-line diff. Security pass: zero in scope (build-time dependency change; the runtime image gains `git` but no application code uses `child_process`/`spawn`/`exec` to invoke it on user input, so the footgun is purely "if you already have RCE you also have git"). **Dependency-compatibility audit** (so implementer doesn't have to redo it per project): every project in the table runs the same general TypeScript/Node stack — `better-sqlite3`, `fastify`, `tsx`, `vitest` — and authd's PR #15/#16 confirmed all of them ship node-24 prebuilds (`better-sqlite3@12.6.2`, `bcrypt@6.0.0`, `fastify@5.7.4`, `tsx@4.21.0`, `vitest@4.0.18`). nanodrop PR #10 reconfirmed the same. movement is currently on `node:20-alpine` (oldest in the fleet) — its `better-sqlite3` and other native deps may need to be bumped to node-24-compatible versions; if `npm ci` after the base bump fails on a native-dep prebuild, **ABANDON for movement specifically** and re-file as a movement-only sub-item so the dep bumps can be handled separately. The other four are guaranteed-compatible per the prior PRs. **Acceptance.** - Every Node-based `Dockerfile` in `~/` reads `FROM node:24-alpine` AND its `RUN apk add` line includes `git`. - For each project, the next deploy CI run on `main` (re-run of latest, or fresh trivial commit) reaches `success`. No `EBADPLATFORM`, no "tarball … seems to be corrupted", no `ENOENT spawn git`. - `npm ci` resolution of any future `git+https://` dep works fleet-wide without per-project Dockerfile patching. - No application behavior changes; each project's test suite green delta is 0. - Two pending bugs in `~/bugs.md` (buchinese, inventory — both filed 2026-05-12) become moot once this chore lands; bug-fixer should ABANDON them with a note pointing at the chore's merge commits. **Future-projects rule.** A standing rule has been added to `~/.claude/CLAUDE.md` ("Node + Alpine Docker base images — `node:24-alpine` + `git`") so every new Node-based project the user (or any Claude session) bootstraps starts with the canonical Dockerfile shape. That rule lives next to the existing iOS-16px-font-size standing rule — same template (file a retrofit chore for current fleet, then add a standing rule to prevent regression for everything new). **Filer notes.** - This chore exists primarily to stop re-discovering the same root cause one project at a time. If the implementer ships it before the buchinese + inventory bugs are picked up by the bug-fixer, those bugs convert to no-ops — file resolution notes against them. - Recommended PR ordering: buchinese first (currently red, highest user-visible value), inventory second (also red), dashcam third (not failing today but ships `ffmpeg` — verify the `apk add` line preservation), movement last (oldest base, riskiest dep audit). authd and nanodrop are no-ops.
brendan added the feature label 2026-05-13 19:01:58 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: brendan/nanodrop#15