fix(docker): bake pnpm via npm to remove Corepack runtime downloads (GRO-1981)
The GRO-1983 fast restoration swapped Corepack's pnpm shim for a real
`npm install -g pnpm@9.15.4` binary, which is the right move. But the
GRO-1997 evidence gate still showed the first `reset-demo-data` pod
(...-nh7vg) hitting `getaddrinfo EAI_AGAIN registry.npmjs.org` before a
retry succeeded — the cache was writable, the cold-cache registry
download wasn't eliminated. This is the durable fix:
1. `ENV COREPACK_ENABLE_DOWNLOAD_FALLBACK=0` in `base` and `runner`:
defence in depth so a Corepack shim can never silently re-download
pnpm, even if it is somehow re-introduced.
2. `ENV HOME=/tmp` in the `migrate`, `seed`, and `reset` stages:
under `readOnlyRootFilesystem: true` + `runAsUser: 1000`, the
default HOME path is read-only, and pnpm fails the first time it
tries to write a config or state file. The job pods already mount a
writable emptyDir at `/tmp`; point HOME there.
3. CI smoke tests for `seed` and `reset` images (matching the existing
`migrate` smoke): point `registry.npmjs.org` at 127.0.0.1 in a
throwaway container, assert `which pnpm` resolves to
`/usr/local/bin/pnpm` (real binary, not shim), and that `pnpm
--version` succeeds without network egress. If Corepack ever sneaks
back in, CI catches it on every PR.
The vestigial `RUN mkdir -p /home/node/.cache/node/corepack` in the
`builder` stage (mentioned in the spec) was already removed in GRO-1909
(commit 0a3eb8a), so nothing to do there.
Follow-on cleanup of the per-job `COREPACK_HOME` env vars and
`node-cache` emptyDir mounts in `groombook/infra` is intentionally
deferred to a coordinated infra PR once the new image is deployed —
keeping the existing infra in place during the transition avoids a
flag-day.
GRO-1985, hardening follow-up to GRO-1984 / GRO-1983.
Closes parent: GRO-1981.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -156,3 +156,32 @@ jobs:
|
||||
${{ github.ref == 'refs/heads/main' && 'git.farh.net/groombook/reset:latest' || '' }}
|
||||
cache-from: type=registry,ref=git.farh.net/groombook/cache:reset
|
||||
cache-to: type=registry,ref=git.farh.net/groombook/cache:reset,mode=max
|
||||
|
||||
- name: Smoke test seed image (blackhole npmjs.org)
|
||||
run: |
|
||||
set -euo pipefail
|
||||
IMAGE="git.farh.net/groombook/seed:${{ steps.version.outputs.tag }}"
|
||||
docker pull "$IMAGE"
|
||||
# GRO-1985: pnpm must be a real binary, not a Corepack shim, and must
|
||||
# not try to reach registry.npmjs.org on invocation.
|
||||
docker run --rm \
|
||||
--add-host registry.npmjs.org:127.0.0.1 \
|
||||
--entrypoint="" \
|
||||
"$IMAGE" \
|
||||
sh -c 'set -e; test "$(which pnpm)" = "/usr/local/bin/pnpm"; pnpm --version'
|
||||
echo "seed image: pnpm resolves to /usr/local/bin/pnpm and runs offline ✓"
|
||||
|
||||
- name: Smoke test reset image (blackhole npmjs.org)
|
||||
run: |
|
||||
set -euo pipefail
|
||||
IMAGE="git.farh.net/groombook/reset:${{ steps.version.outputs.tag }}"
|
||||
docker pull "$IMAGE"
|
||||
# GRO-1985: pnpm must be a real binary, not a Corepack shim, and must
|
||||
# not try to reach registry.npmjs.org on invocation. Validates the
|
||||
# hard requirement from the issue: reset runs offline.
|
||||
docker run --rm \
|
||||
--add-host registry.npmjs.org:127.0.0.1 \
|
||||
--entrypoint="" \
|
||||
"$IMAGE" \
|
||||
sh -c 'set -e; test "$(which pnpm)" = "/usr/local/bin/pnpm"; echo "HOME=$HOME"; pnpm --version'
|
||||
echo "reset image: pnpm resolves to /usr/local/bin/pnpm, HOME=/tmp, runs offline ✓"
|
||||
|
||||
+13
-1
@@ -3,8 +3,12 @@ FROM node:22-alpine AS base
|
||||
# invocations of `pnpm` work without DNS access to registry.npmjs.org.
|
||||
# The corepack shim delegates to corepack, which re-validates against
|
||||
# npmjs.org on first use — that fails in air-gapped UAT seed/migrate/reset
|
||||
# Jobs. GRO-1983 / GRO-1889 / GRO-1909.
|
||||
# Jobs. GRO-1983 / GRO-1889 / GRO-1909 / GRO-1981 / GRO-1985.
|
||||
RUN npm install -g pnpm@9.15.4
|
||||
# Belt-and-braces: disable Corepack's download fallback so that even if a
|
||||
# Corepack shim is somehow invoked at runtime, it will not try to fetch
|
||||
# pnpm from registry.npmjs.org. Belt for the real-binary trousers. GRO-1985.
|
||||
ENV COREPACK_ENABLE_DOWNLOAD_FALLBACK=0
|
||||
WORKDIR /app
|
||||
|
||||
# Install deps
|
||||
@@ -26,6 +30,8 @@ RUN pnpm --filter @groombook/types build && \
|
||||
# Runtime
|
||||
FROM node:22-alpine AS runner
|
||||
RUN npm install -g pnpm@9.15.4
|
||||
# Same defence-in-depth as base: no Corepack fallback. GRO-1985.
|
||||
ENV COREPACK_ENABLE_DOWNLOAD_FALLBACK=0
|
||||
WORKDIR /app
|
||||
ENV NODE_ENV=production
|
||||
|
||||
@@ -46,12 +52,18 @@ CMD ["node", "dist/index.js"]
|
||||
|
||||
# Migrate stage — runs drizzle-kit migrate against the database
|
||||
FROM builder AS migrate
|
||||
# pnpm needs a writable HOME for any config/state it writes. With
|
||||
# readOnlyRootFilesystem: true and runAsUser: 1000, /home/node is read-only.
|
||||
# The job pods mount a writable emptyDir at /tmp; point HOME there. GRO-1985.
|
||||
ENV HOME=/tmp
|
||||
CMD ["pnpm", "--filter", "@groombook/db", "migrate"]
|
||||
|
||||
# Seed stage — populates the database with test data
|
||||
FROM builder AS seed
|
||||
ENV HOME=/tmp
|
||||
CMD ["pnpm", "--filter", "@groombook/db", "seed"]
|
||||
|
||||
# Reset stage — drops all tables, re-runs migrations, and re-seeds
|
||||
FROM builder AS reset
|
||||
ENV HOME=/tmp
|
||||
CMD ["pnpm", "--filter", "@groombook/db", "reset"]
|
||||
|
||||
Reference in New Issue
Block a user