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

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>
This commit is contained in:
Jarvis
2026-06-22 16:42:48 -05:00
parent bf2a6745c8
commit 054551b677
5 changed files with 106 additions and 16 deletions

36
Dockerfile.ci Normal file
View File

@@ -0,0 +1,36 @@
# Pre-baked CI base image for Woodpecker pipelines.
#
# Purpose: eliminate the cold `pnpm install` that dominates every pipeline
# (~731s median). This image ships the native toolchain (no per-run `apk add`)
# AND a warm, content-addressable pnpm store with the dependency tree already
# fetched and its musl native modules (better-sqlite3, node-pty, sqlite3,
# canvas, sharp) compiled ONCE at build time. A pipeline `pnpm install
# --frozen-lockfile --prefer-offline` then resolves from local hard-links in
# tens of seconds.
#
# Rebuilt only when `pnpm-lock.yaml` or this Dockerfile change
# (see .woodpecker/ci-image.yml).
#
# Node version is intentionally pinned to 22 (Active LTS at time of writing).
# The node:22 -> node:24 bump lands as a SEPARATE follow-up PR so the cache
# change carries zero runtime-version variables.
FROM node:22-alpine
# Native toolchain required to compile node-gyp deps on musl, plus the
# postgresql-client used by the test step's pg_isready readiness probe.
RUN apk add --no-cache python3 make g++ postgresql-client
# Pin pnpm to the repo's packageManager version via corepack.
RUN corepack enable && corepack prepare pnpm@10.6.2 --activate
WORKDIR /app
# Pin the store location so the pipeline can point `store-dir` at the same path.
ENV PNPM_HOME=/root/.local/share/pnpm
RUN pnpm config set store-dir /root/.local/share/pnpm/store
# Warm the store + compile native modules once. `pnpm fetch` populates the
# content-addressable store directly from the lockfile (no package.json /
# workspace needed), so a baked store stays valid until the lockfile changes.
COPY pnpm-lock.yaml ./
RUN pnpm fetch --frozen-lockfile