mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-07 04:12:04 +00:00
build: run the image on a Wolfi runner
The dependency and build stages stay on node:22-trixie-slim, where pnpm picks the glibc native packages. The runner moves to wolfi-base, pinned by its index digest: glibc, about fifty packages, and every one of them known to the image scan, Node and the PostgreSQL client included. nodejs-22, tzdata and postgresql-18-client come from apk, so the pgclient stage and its pin guard go. The builder writes its Node module ABI and glibc version out and the runner refuses to build on another ABI or an older glibc. Wolfi's npm is major 12 and runs no install scripts unless package.json allows them, so the Prisma CLI install names the three packages that have one, and the build checks that the schema engine file is there before asking the CLI about it. npm is installed for that step and removed in the same layer; the check that nothing is left asks a fresh shell, whose command cache cannot remember the deleted binary.
This commit is contained in:
1 parent
377e0d0d3a
commit
5d36afb487
9 files changed
+105
-124
No files matched your search
@@ -64,13 +64,6 @@ updates:
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
open-pull-requests-limit: 3
|
||||
ignore:
|
||||
# The Dockerfile's `pgclient` stage carries pg_dump and pg_restore of
|
||||
# the bundled database's major. A major bump there is part of the same
|
||||
# migration decision as the database itself, and the runner's paths and
|
||||
# version checks name major 18, so it arrives by hand, not as a PR.
|
||||
- dependency-name: "postgres"
|
||||
update-types: ["version-update:semver-major"]
|
||||
# The `docker` ecosystem above reads the Dockerfile only. The bundled
|
||||
# database lives in `docker-compose.yml`, which GitHub treats as its own
|
||||
# ecosystem, so without this block a new PostgreSQL minor release never
|
||||
|
||||
@@ -247,7 +247,7 @@ jobs:
|
||||
|
||||
# Blocking since v1.15.20 — a HIGH/CRITICAL finding in the built
|
||||
# image fails the job. For a finding without an upstream fix
|
||||
# (e.g. a base-image CVE Debian has not patched yet), add the CVE
|
||||
# (e.g. a base-image CVE Wolfi has not patched yet), add the CVE
|
||||
# id to a `.trivyignore` file at the repository root — Trivy reads
|
||||
# it automatically — one CVE per line, each with a comment naming
|
||||
# the tracking issue and the re-check condition. Never suppress a
|
||||
|
||||
+77
-73
@@ -1,12 +1,17 @@
|
||||
# ── Base image ─────────────────────────────────────────────
|
||||
# Debian (glibc), not Alpine (musl), in every stage. The native modules the
|
||||
# runtime loads (the @napi-rs/canvas Skia renderer, @node-rs/argon2, the
|
||||
# Prisma schema engine) then run their glibc builds. The arm64 musl Skia
|
||||
# build needs ARMv8.2 (LSE atomics, RCpc loads) and dies with SIGILL on an
|
||||
# ARMv8.0 core such as the Raspberry Pi 4's Cortex-A72; the glibc arm64
|
||||
# build does not use those instructions (#1311). trixie over bookworm: on
|
||||
# the day of the switch the bookworm-slim base carried a CRITICAL finding
|
||||
# (zlib, marked will-not-fix) that trixie-slim does not.
|
||||
# ── Base images ────────────────────────────────────────────
|
||||
# glibc, not musl, in every stage. The native modules the runtime loads (the
|
||||
# @napi-rs/canvas Skia renderer, @node-rs/argon2, the Prisma schema engine)
|
||||
# then run their glibc builds. The arm64 musl Skia build needs ARMv8.2 (LSE
|
||||
# atomics, RCpc loads) and dies with SIGILL on an ARMv8.0 core such as the
|
||||
# Raspberry Pi 4's Cortex-A72; the glibc arm64 build does not use those
|
||||
# instructions (#1311).
|
||||
#
|
||||
# The dependency and build stages run on Debian (node:22-trixie-slim), where
|
||||
# pnpm installs the `-gnu` native packages. The runner is Wolfi: glibc, and
|
||||
# a package set small enough that the image scan reports what the app ships
|
||||
# rather than a general-purpose distribution's base system. A glibc binary
|
||||
# runs on the glibc it was linked against or a newer one; the runner checks
|
||||
# both that and the Node module ABI against the builder at build time.
|
||||
|
||||
# ── Stage 1: Dependencies ──────────────────────────────────
|
||||
FROM node:22-trixie-slim@sha256:2117f713a417f3aff682ac8f5bc2ebbdf1beed51f36e67ab1e0fd3866b6d2d8f AS deps
|
||||
@@ -67,6 +72,10 @@ RUN NODE_OPTIONS=--max-old-space-size=8192 pnpm build
|
||||
# name; see the note there for why guessing was wrong.
|
||||
RUN node -p "require('/app/node_modules/pdfjs-dist/package.json').version" \
|
||||
> /app/.pdfjs-version
|
||||
# The Node module ABI and the glibc the native packages were installed for;
|
||||
# the runner refuses to build on an older glibc or another module ABI.
|
||||
RUN node -p "process.versions.modules + ' ' + process.report.getReport().header.glibcVersionRuntime + ' ' + process.version" \
|
||||
> /app/.native-abi
|
||||
# The same for @napi-rs/canvas, whose native binary must match its JS loader.
|
||||
RUN node -p "require('/app/node_modules/@napi-rs/canvas/package.json').version" \
|
||||
> /app/.canvas-version
|
||||
@@ -76,62 +85,40 @@ RUN node -p "require('/app/node_modules/@napi-rs/canvas/package.json').version"
|
||||
RUN node scripts/verify-message-catalogs.mjs --dir messages \
|
||||
--config src/lib/i18n/config.ts --write /app/.message-locales
|
||||
|
||||
# ── Stage 3: PostgreSQL client tools ───────────────────────
|
||||
# `pg_dump` and `pg_restore` of major 18, and the libpq they link, taken from
|
||||
# the official PostgreSQL image built on the same Debian release. Installing
|
||||
# `postgresql-client-18` instead pulls Perl and Debian's client wrapper
|
||||
# scripts into the runner for two binaries the app calls by name. Pinned to
|
||||
# the same minor as the bundled database in docker-compose.yml; move the two
|
||||
# together. The libpq file is found through the loader rather than by an
|
||||
# architecture path, so the step is the same on amd64 and arm64.
|
||||
FROM postgres:18.6-trixie@sha256:3a4264520e121176619f54ff867d12774bc81e95e7b84db58d63bccdb6144b81 AS pgclient
|
||||
RUN set -eu; \
|
||||
mkdir -p /out/bin /out/lib; \
|
||||
cp /usr/lib/postgresql/18/bin/pg_dump /usr/lib/postgresql/18/bin/pg_restore /out/bin/; \
|
||||
LIBPQ="$(ldd /usr/lib/postgresql/18/bin/pg_dump | awk '$1 == "libpq.so.5" { print $3 }')"; \
|
||||
[ -n "$LIBPQ" ] || { echo "ERROR: pg_dump links no libpq.so.5"; exit 1; }; \
|
||||
cp -L "$LIBPQ" /out/lib/libpq.so.5
|
||||
|
||||
# ── Stage 4: Production runner ─────────────────────────────
|
||||
FROM node:22-trixie-slim@sha256:2117f713a417f3aff682ac8f5bc2ebbdf1beed51f36e67ab1e0fd3866b6d2d8f AS runner
|
||||
# `tzdata` is required so Europe/Berlin schedules (pg-boss cron, locale-aware
|
||||
# timestamp formatting) resolve to the actual offset instead of silently
|
||||
# falling back to UTC; the slim base ships without it.
|
||||
# ── Stage 3: Production runner ─────────────────────────────
|
||||
FROM cgr.dev/chainguard/wolfi-base:latest@sha256:629d9acaceda7746cd9293365eb0d8e8fabf74e91830b05470cfbee20b53f447 AS runner
|
||||
# `nodejs-22` is the runtime; the builder's Node 22 and this one share the
|
||||
# module ABI, which the check below holds. `tzdata` is required so
|
||||
# Europe/Berlin schedules (pg-boss cron, locale-aware timestamp formatting)
|
||||
# resolve to the actual offset instead of silently falling back to UTC.
|
||||
#
|
||||
# `apt-get upgrade` pulls in OS-package security fixes the pinned base digest
|
||||
# has not absorbed yet (the digest pin stays for build reproducibility;
|
||||
# security patches from the Debian archive override it deliberately —
|
||||
# CVE-2026-14456 in openssl was the first instance the container scan
|
||||
# caught between base-image rebuilds).
|
||||
# `apk upgrade` pulls in package security fixes the pinned base digest has
|
||||
# not absorbed yet (the digest pin stays for build reproducibility; fixes
|
||||
# from the Wolfi repository override it deliberately — CVE-2026-14456 in
|
||||
# openssl was the first instance the container scan caught between
|
||||
# base-image rebuilds).
|
||||
#
|
||||
# `pg_dump` and `pg_restore` serve the nightly database dump, the
|
||||
# pre-migration dump in the entrypoint and the `db-restore` service
|
||||
# (docs/ops/disaster-recovery.md). Major 18 matches the bundled database; a
|
||||
# client dumps servers of its own major and older, so it also serves an
|
||||
# install still on its own Postgres 16. Debian trixie ships major 17, so the
|
||||
# two binaries and their libpq come from the `pgclient` stage above; the
|
||||
# Debian libraries libpq links (Kerberos, LDAP, SASL) come from apt below, so
|
||||
# the build-time upgrade and the image scan see them. The copied binaries are
|
||||
# outside dpkg: the scan does not see them and `apt-get upgrade` does not
|
||||
# patch them, so a PostgreSQL client fix arrives by moving the `pgclient`
|
||||
# pin.
|
||||
# `postgresql-18-client` carries `pg_dump` and `pg_restore` for the nightly
|
||||
# database dump, the pre-migration dump in the entrypoint and the
|
||||
# `db-restore` service (docs/ops/disaster-recovery.md). Major 18 matches the
|
||||
# bundled database; a client dumps servers of its own major and older, so it
|
||||
# also serves an install still on its own Postgres 16.
|
||||
#
|
||||
# `ca-certificates` is the system trust store the OpenSSL-linked tools use
|
||||
# (the Prisma schema engine, libpq) for a TLS connection to the database.
|
||||
RUN set -eux; \
|
||||
export DEBIAN_FRONTEND=noninteractive; \
|
||||
apt-get update; \
|
||||
apt-get upgrade -y --no-install-recommends; \
|
||||
apt-get install -y --no-install-recommends \
|
||||
ca-certificates tzdata libgssapi-krb5-2 libldap2 libsasl2-2; \
|
||||
apt-get clean; \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
|
||||
COPY --from=pgclient /out/bin/ /usr/lib/postgresql/18/bin/
|
||||
COPY --from=pgclient /out/lib/libpq.so.5 /usr/lib/postgresql/18/lib/libpq.so.5
|
||||
RUN echo /usr/lib/postgresql/18/lib > /etc/ld.so.conf.d/postgresql-18.conf && \
|
||||
ldconfig && \
|
||||
! ldd /usr/lib/postgresql/18/bin/pg_dump /usr/lib/postgresql/18/bin/pg_restore | grep -q "not found"
|
||||
ENV PATH=/usr/lib/postgresql/18/bin:$PATH
|
||||
# busybox, part of the base, is the shell the entrypoint runs under and
|
||||
# carries `tar`/`gzip` for the GeoLite2 extraction and `addgroup`/`adduser`.
|
||||
RUN apk add --no-cache nodejs-22 tzdata postgresql-18-client && \
|
||||
apk upgrade --no-cache && \
|
||||
mkdir -p /usr/local/bin
|
||||
COPY --from=builder /app/.native-abi /tmp/.native-abi
|
||||
RUN set -e; \
|
||||
read -r BUILD_ABI BUILD_GLIBC BUILD_NODE < /tmp/.native-abi; \
|
||||
rm -f /tmp/.native-abi; \
|
||||
RUN_ABI="$(node -p process.versions.modules)"; \
|
||||
RUN_GLIBC="$(node -p 'process.report.getReport().header.glibcVersionRuntime')"; \
|
||||
[ "$RUN_ABI" = "$BUILD_ABI" ] || { echo "ERROR: runner Node module ABI $RUN_ABI, builder $BUILD_ABI"; exit 1; }; \
|
||||
[ "$(printf '%s\n%s\n' "$BUILD_GLIBC" "$RUN_GLIBC" | sort -V | head -1)" = "$BUILD_GLIBC" ] || \
|
||||
{ echo "ERROR: runner glibc $RUN_GLIBC is older than the builder's $BUILD_GLIBC"; exit 1; }; \
|
||||
echo "native ABI $RUN_ABI (builder $BUILD_NODE, runner $(node -v)); glibc $BUILD_GLIBC -> $RUN_GLIBC"
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
@@ -162,9 +149,8 @@ ENV NEXT_PUBLIC_APP_VERSION=$NEXT_PUBLIC_APP_VERSION
|
||||
ARG NEXT_PUBLIC_APP_BUILD_SHA
|
||||
ENV NEXT_PUBLIC_APP_BUILD_SHA=$NEXT_PUBLIC_APP_BUILD_SHA
|
||||
|
||||
RUN groupadd --system --gid 1001 nodejs && \
|
||||
useradd --system --uid 1001 --gid nodejs --create-home --home-dir /home/nextjs \
|
||||
--shell /usr/sbin/nologin nextjs
|
||||
RUN addgroup -S -g 1001 nodejs && \
|
||||
adduser -S -u 1001 -G nodejs -h /home/nextjs -s /sbin/nologin nextjs
|
||||
|
||||
# Where the database dumps go (`DB_BACKUP_DIR`). Owned by the app user, so a
|
||||
# fresh named volume mounted here starts with that owner: Docker fills an
|
||||
@@ -327,8 +313,16 @@ RUN node scripts/db-dump.mjs 2>&1 | grep -q "db-dump: usage:" && \
|
||||
|
||||
# Install the migration CLI, its config dependency, and a pinned launcher for
|
||||
# the maintenance scripts already shipped in the standalone tree. The Prisma
|
||||
# config loads dotenv from /app, so expose the isolated copy there before
|
||||
# stripping npm/Corepack and their caches from the runtime surface.
|
||||
# config loads dotenv from /app, so expose the isolated copy there. npm is
|
||||
# installed for this one step and removed in the same layer, with its cache,
|
||||
# so no package manager is left in the runtime surface.
|
||||
#
|
||||
# Wolfi's npm is major 12, which runs no dependency install scripts unless
|
||||
# package.json lists the package under `allowScripts`. Three packages here
|
||||
# have one: `@prisma/engines` downloads the schema engine `migrate deploy`
|
||||
# runs, `prisma` checks its environment, and `esbuild` (under tsx) checks
|
||||
# its binary. Without the field the install succeeds and ships no schema
|
||||
# engine, so the check after this step asks the CLI for it.
|
||||
#
|
||||
# This install belongs to npm, not pnpm, so the `overrides` block in
|
||||
# `pnpm-workspace.yaml` does not reach it: a package pinned there for the app
|
||||
@@ -354,19 +348,29 @@ RUN node scripts/db-dump.mjs 2>&1 | grep -q "db-dump: usage:" && \
|
||||
# npm install never reads. Rebuilding this install both ways confirms it:
|
||||
# without the two fields below npm resolves 1.19.11 and 1.2.0, with them it
|
||||
# resolves 1.19.17 and 1.4.2, and `prisma --version` still reports 7.8.0.
|
||||
RUN mkdir -p /opt/prisma-cli && \
|
||||
RUN apk add --no-cache npm && \
|
||||
mkdir -p /opt/prisma-cli && \
|
||||
cd /opt/prisma-cli && \
|
||||
npm init -y && \
|
||||
npm pkg set 'overrides.deepmerge-ts=^8.0.0' && \
|
||||
npm pkg set 'overrides.mysql2=^3.22.0' && \
|
||||
npm pkg set 'overrides.@hono/node-server=^1.19.15' && \
|
||||
npm pkg set 'overrides.valibot=^1.4.2' && \
|
||||
npm pkg set 'allowScripts.@prisma/engines=true' 'allowScripts.prisma=true' 'allowScripts.esbuild=true' --json && \
|
||||
npm install --omit=dev prisma@7.10.1 @prisma/engines@7.10.1 tsx@4.23.1 dotenv@18.0.9 && \
|
||||
ln -sfn /opt/prisma-cli/node_modules/.bin/tsx /usr/local/bin/healthlog-tsx && \
|
||||
ln -sfn /opt/prisma-cli/node_modules/dotenv /app/node_modules/dotenv && \
|
||||
ln -sfn /opt/prisma-cli/node_modules/prisma /app/node_modules/prisma && \
|
||||
rm -rf /usr/local/lib/node_modules/npm /usr/local/lib/node_modules/corepack /root/.cache/node/corepack /root/.npm && \
|
||||
rm -f /usr/local/bin/npm /usr/local/bin/npx /usr/local/bin/corepack /usr/local/bin/pnpm /usr/local/bin/pnpx
|
||||
apk del --no-cache npm && \
|
||||
rm -rf /root/.npm /root/.cache && \
|
||||
sh -c '! command -v npm && ! command -v npx && ! command -v corepack && ! command -v pnpm'
|
||||
|
||||
# The schema engine the entrypoint's `migrate deploy` runs is in place.
|
||||
# Look for the file before asking the CLI: a CLI that finds none downloads
|
||||
# one, which would hide the missing install script here and fail offline.
|
||||
RUN ls /opt/prisma-cli/node_modules/@prisma/engines/schema-engine-* >/dev/null && \
|
||||
node /opt/prisma-cli/node_modules/prisma/build/index.js --version 2>&1 | \
|
||||
grep -q "Schema Engine *: schema-engine-cli [0-9a-f]* (at "
|
||||
|
||||
# Same proof for the intake-repair script under the launcher it is documented
|
||||
# with: its `pg` import resolves, so it reaches its own DATABASE_URL check.
|
||||
@@ -389,7 +393,7 @@ RUN env -u DATABASE_URL healthlog-tsx scripts/repair-intake-anomalies.ts 2>&1 |
|
||||
# user so the runtime worker can WRITE it: a self-hoster who sets
|
||||
# MAXMIND_LICENSE_KEY on the published image has the databases fetched into
|
||||
# this directory at runtime (src/lib/geo/geolite2-fetch.ts) with no rebuild or
|
||||
# manual mount. `tar`/`gzip` for the extraction are part of the Debian base. A
|
||||
# manual mount. `tar`/`gzip` for the extraction come with busybox. A
|
||||
# read-only bind mount at this path (the bring-your-own-database route) still
|
||||
# works — the runtime fetch just fails soft and the mounted files stand.
|
||||
RUN mkdir -p /opt/geolite2 && chown nextjs:nodejs /opt/geolite2
|
||||
@@ -419,8 +423,8 @@ ENV HOSTNAME="0.0.0.0"
|
||||
# Five retries: a pool saturated for a minute during a boot storm makes the
|
||||
# probe's 2 s database check miss once or twice without marking the container
|
||||
# unhealthy (docker-compose.yml explains the numbers). The probe is Node's own
|
||||
# fetch: the Debian base carries neither wget nor curl, and installing one for
|
||||
# this line would add a TLS stack to the image's scan surface. A non-2xx
|
||||
# fetch: it needs nothing beyond the runtime, the same probe works on any
|
||||
# base, and no HTTP client has to be installed for one line. A non-2xx
|
||||
# answer, a refused connection and the 4 s abort all exit 1.
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=10m --retries=5 \
|
||||
CMD ["node", "-e", "fetch('http://127.0.0.1:3000/api/health',{signal:AbortSignal.timeout(4000)}).then(r=>process.exit(r.ok?0:1),()=>process.exit(1))"]
|
||||
|
||||
@@ -105,7 +105,7 @@ The App Store listing carries a small price, which exists to cover Apple's yearl
|
||||
|
||||
## Tech stack
|
||||
|
||||
Next.js 16 (App Router, React Server Components), TypeScript strict, PostgreSQL 18 with Prisma, pg-boss for jobs, Tailwind CSS 4 + shadcn/ui, Recharts, WebAuthn passkeys, Vitest + Playwright, multi-stage Debian (glibc) Docker image. The OpenAPI 3.1 contract for native clients is generated from the Zod schemas at [`docs/api/openapi.yaml`](docs/api/openapi.yaml).
|
||||
Next.js 16 (App Router, React Server Components), TypeScript strict, PostgreSQL 18 with Prisma, pg-boss for jobs, Tailwind CSS 4 + shadcn/ui, Recharts, WebAuthn passkeys, Vitest + Playwright, multi-stage glibc Docker image (Debian build, Wolfi runtime). The OpenAPI 3.1 contract for native clients is generated from the Zod schemas at [`docs/api/openapi.yaml`](docs/api/openapi.yaml).
|
||||
|
||||
## Documentation
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ already installed, longer if a TLS-fronted public hostname is in scope
|
||||
- **Docker 24+ with Compose v2.** The bundled `docker-compose.yml`
|
||||
pulls a multi-arch image from GitHub Container Registry (linux/amd64
|
||||
and linux/arm64), so a Raspberry Pi 4 or 5 or an x86 VPS all work
|
||||
unchanged. The image is Debian-based (glibc). Document thumbnails and
|
||||
unchanged. The image runs on glibc (a Wolfi base). Document thumbnails and
|
||||
scanned-PDF rendering use a native renderer that runs on every arm64
|
||||
board, the Pi 4 included, and on x86-64 CPUs with AVX2; on an older x86
|
||||
CPU (Celeron/Atom-class NAS chips) it switches itself off and those two
|
||||
|
||||
@@ -224,7 +224,8 @@ describe("release container inputs", () => {
|
||||
const dockerfile = readRepoFile("Dockerfile");
|
||||
const fromLines = dockerfile.match(/^FROM node:[^\n]+$/gm) ?? [];
|
||||
|
||||
expect(fromLines).toHaveLength(3);
|
||||
// deps and builder; the runner is Wolfi (canvas-binary-hoist-guard).
|
||||
expect(fromLines).toHaveLength(2);
|
||||
expect(new Set(fromLines.map((line) => line.split(" AS ")[0]))).toEqual(
|
||||
new Set([
|
||||
"FROM node:22-trixie-slim@sha256:2117f713a417f3aff682ac8f5bc2ebbdf1beed51f36e67ab1e0fd3866b6d2d8f",
|
||||
@@ -243,8 +244,11 @@ describe("release container inputs", () => {
|
||||
expect(
|
||||
dockerfile.match(/corepack prepare pnpm@11\.15\.1 --activate/g),
|
||||
).toHaveLength(2);
|
||||
// The runner installs npm for the Prisma CLI step only, removes it in the
|
||||
// same layer, and fails the build if any package manager is left.
|
||||
expect(dockerfile).toContain("apk del --no-cache npm");
|
||||
expect(dockerfile).toContain(
|
||||
"rm -rf /usr/local/lib/node_modules/npm /usr/local/lib/node_modules/corepack /root/.cache/node/corepack",
|
||||
"sh -c '! command -v npm && ! command -v npx && ! command -v corepack && ! command -v pnpm'",
|
||||
);
|
||||
expect(dockerfile).toContain("tsx@4.23.1");
|
||||
expect(dockerfile).toContain(
|
||||
|
||||
@@ -61,20 +61,26 @@ describe("canvas binary hoist", () => {
|
||||
expect(runner).not.toMatch(/@napi-rs\+canvas[^"'\s]*@\*/);
|
||||
});
|
||||
|
||||
it("builds every Node stage on the glibc Debian base", () => {
|
||||
const froms = DOCKERFILE.match(/^FROM \S+/gm) ?? [];
|
||||
const node = froms.filter((from) => from.startsWith("FROM node:"));
|
||||
expect(node).toHaveLength(3);
|
||||
for (const from of node) {
|
||||
expect(from).toMatch(/^FROM node:22-trixie-slim@sha256:[0-9a-f]{64}$/);
|
||||
}
|
||||
// The only other stage supplies the PostgreSQL client tools, built on
|
||||
// the same Debian release so its binaries find their libraries.
|
||||
expect(froms.filter((from) => !from.startsWith("FROM node:"))).toEqual([
|
||||
it("installs on Debian and runs on Wolfi, both glibc", () => {
|
||||
const froms = DOCKERFILE.match(/^FROM \S+ AS \S+$/gm) ?? [];
|
||||
expect(froms).toEqual([
|
||||
expect.stringMatching(
|
||||
/^FROM postgres:18\.\d+-trixie@sha256:[0-9a-f]{64}$/,
|
||||
/^FROM node:22-trixie-slim@sha256:[0-9a-f]{64} AS deps$/,
|
||||
),
|
||||
expect.stringMatching(
|
||||
/^FROM node:22-trixie-slim@sha256:[0-9a-f]{64} AS builder$/,
|
||||
),
|
||||
expect.stringMatching(
|
||||
/^FROM cgr\.dev\/chainguard\/wolfi-base:latest@sha256:[0-9a-f]{64} AS runner$/,
|
||||
),
|
||||
]);
|
||||
// The native packages pnpm picked in the builder must load in the
|
||||
// runner: same module ABI, and a glibc no older than the builder's.
|
||||
expect(stage("builder")).toContain("> /app/.native-abi");
|
||||
expect(stage("runner")).toContain(
|
||||
'[ "$RUN_ABI" = "$BUILD_ABI" ] || { echo "ERROR: runner Node module ABI',
|
||||
);
|
||||
expect(stage("runner")).toContain("is older than the builder's");
|
||||
});
|
||||
|
||||
it("fails the build when the copied binary is another version", () => {
|
||||
|
||||
@@ -40,32 +40,6 @@ describe("the bundled database pin", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("ships pg_dump of the same minor the bundled database runs", () => {
|
||||
const db = /^\s*image:\s*postgres:(\d+\.\d+)-alpine$/m.exec(
|
||||
read("docker-compose.yml"),
|
||||
);
|
||||
const client =
|
||||
/^FROM postgres:(\d+\.\d+)-trixie@sha256:[0-9a-f]{64} AS pgclient$/m.exec(
|
||||
read("Dockerfile"),
|
||||
);
|
||||
expect(db, "the compose file pins the database").not.toBeNull();
|
||||
expect(client, "the Dockerfile has a pinned pgclient stage").not.toBeNull();
|
||||
// The two pins are one decision; a Dependabot PR moves them separately.
|
||||
expect(client![1]).toBe(db![1]);
|
||||
});
|
||||
|
||||
it("keeps a major bump of the client tools out of the Dockerfile watch too", () => {
|
||||
const dependabot = read(".github/dependabot.yml");
|
||||
const docker =
|
||||
/package-ecosystem:\s*"docker"\n([\s\S]*?)(?=\n\s*- package-ecosystem:)/.exec(
|
||||
dependabot,
|
||||
);
|
||||
expect(docker, "a docker ecosystem entry").not.toBeNull();
|
||||
expect(docker![1]).toMatch(
|
||||
/dependency-name:\s*"postgres"[\s\S]{0,200}version-update:semver-major/,
|
||||
);
|
||||
});
|
||||
|
||||
it("lets a major version through only as a decision, never as an update PR", () => {
|
||||
const dependabot = read(".github/dependabot.yml");
|
||||
// The first run of the compose ecosystem proposed 16.15 -> 18.6. A
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
* restart loop (live incident: a Synology DS1520+ self-host, exit code 132
|
||||
* ~18 s after every boot once the thumbnail backfill touched Skia).
|
||||
*
|
||||
* The published image is Debian (glibc) and runs the glibc build. Under an
|
||||
* The published image runs on glibc (Wolfi) and loads the glibc build. Under an
|
||||
* emulated AVX2-less Atom core that build drew a canvas and rendered
|
||||
* a PDF page to JPEG where the musl build died, but it still carries AVX2 code
|
||||
* and not every path it can take was exercised, so the x64 gate stays closed
|
||||
@@ -26,7 +26,7 @@
|
||||
* fallback, and dies with SIGILL as soon as the module loads on an ARMv8.0
|
||||
* core such as the Raspberry Pi 4's Cortex-A72 (#1311). The glibc arm64 build
|
||||
* uses neither and runs there, which is why the published image moved off
|
||||
* Alpine. The musl branch stays for a host that runs the app on musl itself,
|
||||
* musl. The musl branch stays for a host that runs the app on musl itself,
|
||||
* from source on Alpine or from an image built on it.
|
||||
*
|
||||
* Detection reads `/proc/cpuinfo` once on Linux: x64 requires the `avx2`
|
||||
|
||||
Reference in new issue
Block a user