#236: pin the Node version #257

Merged
fable-5 merged 2 commits from feat/236-pin-node-version into main 2026-07-31 04:32:54 +02:00
12 changed files with 59 additions and 15 deletions

View File

@ -86,7 +86,7 @@ jobs:
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 22
node-version-file: .node-version
cache: pnpm
- name: Install dependencies

View File

@ -58,13 +58,46 @@ jobs:
exit 1
fi
# One authoritative Node version (issue #236): `.node-version` is the
# pin; every Dockerfile image tag and the engines floor must match it
# exactly, and workflows select Node only through node-version-file.
# Raising Node = update .node-version, every `FROM node:` tag and the
# engines floor in ONE commit (procedure: docs/architecture/operations.md
# §Update strategy). The bracketed grep pattern keeps this step from
# matching its own source (same trick as the secret fence above).
- name: Node version pin is consistent
run: |
set -euo pipefail
ver="$(cat .node-version)"
echo "pinned Node version: $ver"
bad=0
for f in apps/*/Dockerfile; do
if grep '^FROM node:' "$f" | grep -v "node:${ver}-alpine"; then
echo "$f pins a different Node image than node:${ver}-alpine"
bad=1
fi
done
if grep -rn "node-version[:] " .gitea/workflows; then
echo "workflows must use node-version-file, not a literal version"
bad=1
fi
if grep -rnE 'node:[0-9][^ ]*-alpine' .gitea/workflows | grep -v "node:${ver}-alpine"; then
echo "a workflow references a different node image than node:${ver}-alpine"
bad=1
fi
if ! grep -q "\"node\": \">=${ver}\"" package.json; then
echo "package.json engines floor does not match ${ver}"
bad=1
fi
exit "$bad"
- name: Set up pnpm
uses: pnpm/action-setup@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 22
node-version-file: .node-version
cache: pnpm
- name: Install dependencies
@ -119,7 +152,7 @@ jobs:
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 22
node-version-file: .node-version
cache: pnpm
- name: Install dependencies
@ -594,7 +627,7 @@ jobs:
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 22
node-version-file: .node-version
cache: pnpm
- name: Install dependencies

View File

@ -55,7 +55,7 @@ jobs:
} > comment.md
# JSON-encode via a node container — the runner image guarantees
# only git/curl/docker, not python or node.
docker run --rm -i node:22.15-alpine node -e \
docker run --rm -i node:22.15.1-alpine node -e \
'const fs=require("fs");process.stdout.write(JSON.stringify({body:fs.readFileSync(0,"utf8")}))' \
< comment.md > comment.json
curl -sf -X POST \

View File

@ -54,7 +54,7 @@ jobs:
echo '_No database migrations in this release._'
fi
} > notes.md
TAG=$TAG docker run --rm -i -e TAG node:22.15-alpine node -e \
TAG=$TAG docker run --rm -i -e TAG node:22.15.1-alpine node -e \
'const fs=require("fs");const body=fs.readFileSync(0,"utf8");process.stdout.write(JSON.stringify({tag_name:process.env.TAG,name:process.env.TAG,body}))' \
< notes.md > release.json
curl -sf -X POST \

1
.node-version Normal file
View File

@ -0,0 +1 @@
22.15.1

View File

@ -1,7 +1,7 @@
# Build context is the repository root (workspace build):
# docker build -f apps/api/Dockerfile .
FROM node:22.15-alpine AS build
FROM node:22.15.1-alpine AS build
WORKDIR /repo
RUN npm install -g pnpm@11
COPY pnpm-workspace.yaml pnpm-lock.yaml package.json tsconfig.base.json ./
@ -23,7 +23,7 @@ RUN pnpm install --frozen-lockfile --filter @dorfteich/api... \
&& cp -r apps/api/dist /out/dist \
&& cp -r /repo/fonts /out/fonts
FROM node:22.15-alpine
FROM node:22.15.1-alpine
ARG APP_VERSION=0.0.0-dev
# Default the data dirs to the writable, node-owned locations created below, so
# the image works out of the box even where compose does not set them; compose

View File

@ -1,7 +1,7 @@
# Build context is the repository root (workspace build):
# docker build -f apps/backup/Dockerfile .
FROM node:22.15-alpine AS build
FROM node:22.15.1-alpine AS build
WORKDIR /repo
RUN npm install -g pnpm@11
COPY pnpm-workspace.yaml pnpm-lock.yaml package.json tsconfig.base.json ./
@ -14,7 +14,7 @@ RUN pnpm install --frozen-lockfile --filter @dorfteich/backup... \
&& pnpm --filter @dorfteich/backup deploy --prod --legacy /out \
&& cp -r apps/backup/dist /out/dist
FROM node:22.15-alpine
FROM node:22.15.1-alpine
ARG APP_VERSION=0.0.0-dev
ENV NODE_ENV=production APP_VERSION=${APP_VERSION} \
# Baked-in volume paths (self-sufficient without compose env, like the

View File

@ -1,7 +1,7 @@
# Build context is the repository root (workspace build):
# docker build -f apps/collab/Dockerfile .
FROM node:22.15-alpine AS build
FROM node:22.15.1-alpine AS build
WORKDIR /repo
RUN npm install -g pnpm@11
COPY pnpm-workspace.yaml pnpm-lock.yaml package.json tsconfig.base.json ./
@ -14,7 +14,7 @@ RUN pnpm install --frozen-lockfile --filter @dorfteich/collab... \
&& pnpm --filter @dorfteich/collab deploy --prod --legacy /out \
&& cp -r apps/collab/dist /out/dist
FROM node:22.15-alpine
FROM node:22.15.1-alpine
ARG APP_VERSION=0.0.0-dev
ENV NODE_ENV=production APP_VERSION=${APP_VERSION}
WORKDIR /app

View File

@ -1,7 +1,7 @@
# Build context is the repository root (workspace build):
# docker build -f apps/web/Dockerfile .
FROM node:22.15-alpine AS build
FROM node:22.15.1-alpine AS build
ARG APP_VERSION=0.0.0-dev
WORKDIR /repo
RUN npm install -g pnpm@11

View File

@ -169,6 +169,16 @@ not a copy of the purged page.
- **Base image / dependency hygiene**: monthly dependency-update story
(renovate-style batch PR); security advisories for pinned images tracked
in the release checklist.
- **Toolchain pin (issue #236)**: `.node-version` is the single
authoritative Node version. CI/CD select Node exclusively via
`node-version-file`, every Dockerfile pins `node:<version>-alpine`, and
the `engines.node` floor in `package.json` states the same version
(open-ended upwards — a newer local Node keeps working; reproducibility
rests on the images and CI, not the laptop). An early CI step fails on
any drift between those places. Raising Node (e.g. for a security fix):
update `.node-version`, all four Dockerfiles and the engines floor in
ONE commit and let CI confirm. pnpm is pinned the same way via
`packageManager`.
## Capacity & limits (initial values, instance-tunable)

View File

@ -255,7 +255,7 @@ fehlten — als Issues angelegt:
· 1 AT · #234 (M24, I-23)
- [x] `page_links.target_slug`-Residuum nach Purge entscheiden
· 0,5 AT · #235 (M24, I-24)
- [ ] Node-Version pinnen — Voraussetzung für #219 · 0,5 AT · #236 (M25, I-26)
- [x] Node-Version pinnen — Voraussetzung für #219 · 0,5 AT · #236 (M25, I-26)
Ohne eigenes Issue: IndexedDB-Kopie auf Endgeräten (I-25) — als
Akzeptanzkriterium in #226 (Abgrenzungserklärung) und #231

View File

@ -5,7 +5,7 @@
"description": "Dorfteich — an open-source wiki system with real-time collaboration",
"license": "MIT",
"engines": {
"node": ">=22"
"node": ">=22.15.1"
},
"packageManager": "pnpm@11.9.0",
"scripts": {