Phase 1 of the Woodpecker CI caching fix: a pre-baked CI base image kills the cold pnpm install that dominates every pipeline (median ~731s, paid twice per push).
Changes
Dockerfile.ci — node:22-alpine + python3 make g++ postgresql-client + corepack prepare [email protected] + pnpm fetch --frozen-lockfile (warms the content-addressable store and compiles musl natives — better-sqlite3, node-pty, sqlite3, canvas, sharp — once at build time).
.woodpecker/ci-image.yml — new kaniko pipeline that builds & pushes git.mosaicstack.dev/mosaicstack/stack/ci-base as :latest + a :lock-<hash> tag. Reuses the exact kaniko/auth block from publish.yml. Triggers only when pnpm-lock.yaml or Dockerfile.ci change (plus tags).
.woodpecker/ci.yml & .woodpecker/publish.yml — &node_image anchor → baked ci-base:latest; dropped per-run apk add; install is now pnpm install --frozen-lockfile --prefer-offline. Step graph / tests / postgres service unchanged.
Framework monorepo template (packages/mosaic/framework/tools/quality/templates/monorepo/.woodpecker.yml) — single cached install other steps depend on, instead of re-running npm ci in 6 steps, so scaffolded repos inherit the fix. Kept generic (npm-based).
node 22→24 bump: deliberately NOT here — separate follow-up PR (zero version variables while landing the cache change).
Phase 2 (durable RWX Longhorn PVC for the pnpm store): out of scope, tracked separately.
Validation
All four YAML files parse (yaml.safe_load); *node_image anchors resolve to the baked image in every node step (kaniko steps in publish.yml unaffected). Pipeline NOT run by author — ci-base:latest is built by ci-image.yml on first merge to main.
## Summary
Phase 1 of the Woodpecker CI caching fix: a pre-baked CI base image kills the cold `pnpm install` that dominates every pipeline (median **~731s**, paid twice per push).
### Changes
- **`Dockerfile.ci`** — `node:22-alpine` + `python3 make g++ postgresql-client` + `corepack prepare [email protected]` + `pnpm fetch --frozen-lockfile` (warms the content-addressable store and compiles musl natives — better-sqlite3, node-pty, sqlite3, canvas, sharp — once at build time).
- **`.woodpecker/ci-image.yml`** — new kaniko pipeline that builds & pushes `git.mosaicstack.dev/mosaicstack/stack/ci-base` as `:latest` + a `:lock-<hash>` tag. Reuses the exact kaniko/auth block from `publish.yml`. Triggers only when `pnpm-lock.yaml` or `Dockerfile.ci` change (plus tags).
- **`.woodpecker/ci.yml`** & **`.woodpecker/publish.yml`** — `&node_image` anchor → baked `ci-base:latest`; dropped per-run `apk add`; install is now `pnpm install --frozen-lockfile --prefer-offline`. Step graph / tests / postgres service unchanged.
- **Framework monorepo template** (`packages/mosaic/framework/tools/quality/templates/monorepo/.woodpecker.yml`) — single cached install other steps depend on, instead of re-running `npm ci` in 6 steps, so scaffolded repos inherit the fix. Kept generic (npm-based).
### Expected savings
Install **~731s → ~30–60s warm** (~11 min/workflow, ~20 min/push).
### Scope / out of scope
- **node 22→24 bump:** deliberately NOT here — separate follow-up PR (zero version variables while landing the cache change).
- **Phase 2 (durable RWX Longhorn PVC for the pnpm store):** out of scope, tracked separately.
### Validation
All four YAML files parse (`yaml.safe_load`); `*node_image` anchors resolve to the baked image in every node step (kaniko steps in publish.yml unaffected). Pipeline NOT run by author — `ci-base:latest` is built by `ci-image.yml` on first merge to main.
Board report: `docs/reports/woodpecker-ci-cache-board-2026-06-22.md` (jarvis-brain), Phase 1 + §5.
**No self-merge — needs review (reviewer ≠ author).**
Fixes #634
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Phase 1 of the Woodpecker CI caching fix: a pre-baked CI base image kills the cold
pnpm installthat dominates every pipeline (median ~731s, paid twice per push).Changes
Dockerfile.ci—node:22-alpine+python3 make g++ postgresql-client+corepack prepare [email protected]+pnpm fetch --frozen-lockfile(warms the content-addressable store and compiles musl natives — better-sqlite3, node-pty, sqlite3, canvas, sharp — once at build time)..woodpecker/ci-image.yml— new kaniko pipeline that builds & pushesgit.mosaicstack.dev/mosaicstack/stack/ci-baseas:latest+ a:lock-<hash>tag. Reuses the exact kaniko/auth block frompublish.yml. Triggers only whenpnpm-lock.yamlorDockerfile.cichange (plus tags)..woodpecker/ci.yml&.woodpecker/publish.yml—&node_imageanchor → bakedci-base:latest; dropped per-runapk add; install is nowpnpm install --frozen-lockfile --prefer-offline. Step graph / tests / postgres service unchanged.packages/mosaic/framework/tools/quality/templates/monorepo/.woodpecker.yml) — single cached install other steps depend on, instead of re-runningnpm ciin 6 steps, so scaffolded repos inherit the fix. Kept generic (npm-based).Expected savings
Install ~731s → ~30–60s warm (~11 min/workflow, ~20 min/push).
Scope / out of scope
Validation
All four YAML files parse (
yaml.safe_load);*node_imageanchors resolve to the baked image in every node step (kaniko steps in publish.yml unaffected). Pipeline NOT run by author —ci-base:latestis built byci-image.ymlon first merge to main.Board report:
docs/reports/woodpecker-ci-cache-board-2026-06-22.md(jarvis-brain), Phase 1 + §5.No self-merge — needs review (reviewer ≠ author).
Fixes #634
054551b677to9da71bd861