ci: eliminate cold pnpm install via pre-baked CI base image (Phase 1) #635

Merged
jason.woltje merged 1 commits from chore/ci-cache-phase1-prebaked-image into main 2026-06-22 22:50:22 +00:00
Owner

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.cinode:22-alpine + python3 make g++ postgresql-client + corepack prepare pnpm@10.6.2 + 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

## 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 pnpm@10.6.2` + `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
jason.woltje added 1 commit 2026-06-22 21:43:07 +00:00
ci: eliminate cold pnpm install via pre-baked CI base image (Phase 1)
Some checks failed
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/pr/ci Pipeline failed
054551b677
Every pipeline ran a cold pnpm install (network fetch + musl native
rebuilds + apk add python3 make g++), median ~731s, paid twice per push.

Phase 1 (no cluster access, repo commits only):
- Dockerfile.ci: node:22-alpine + python3/make/g++/postgresql-client +
  pnpm@10.6.2 + pnpm fetch to warm the store and compile natives once.
- .woodpecker/ci-image.yml: kaniko build/push of ci-base:latest + a
  lockfile-hash tag, triggered only when pnpm-lock.yaml or Dockerfile.ci
  change. Reuses the publish.yml kaniko/auth pattern.
- ci.yml + publish.yml: install from the baked ci-base:latest, drop the
  per-run apk add, use pnpm install --frozen-lockfile --prefer-offline.
- Framework monorepo template: single cached install other steps depend
  on instead of re-running npm ci across 6 steps.

Node 22->24 bump is a separate follow-up PR. Phase 2 (RWX Longhorn PVC)
is out of scope. Expected install ~731s -> ~30-60s.

Refs #634

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
jason.woltje force-pushed chore/ci-cache-phase1-prebaked-image from 054551b677 to 9da71bd861 2026-06-22 21:50:44 +00:00 Compare
jason.woltje merged commit 94e5cd7a81 into main 2026-06-22 22:50:22 +00:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mosaicstack/stack#635