fix(image): take scheme-less health-check URLs, anchor the compose pin, say which tools left

This commit is contained in:
Marc-André Bombeck committed 2026-11-04 14:32:58 +01:00
1 parent fb143a2d47
commit 27b054d8c4
5 files changed
+28 -10

No files matched your search

+8 -5
View File
@@ -58,11 +58,14 @@
password hashing and the Prisma engine run their glibc builds. The
published arm64 image renders PDF pages on a Raspberry Pi 4 again, where
the Alpine build of the renderer needs CPU features an ARMv8.0 core
lacks. Node security releases now reach the image with the official
Node image, usually within days. The image carries no package manager:
npm, npx, corepack and yarn are removed. `pg_dump` and `pg_restore` are
PostgreSQL 18.6, the bundled database's version; `psql` is no longer in
the app image, so run it in the `db` container. A health check that
lacks. Node security releases now come with the official Node image,
which HealthLog pins and moves forward with its releases. The image
carries no package manager: npm, npx, corepack and yarn are removed.
`pg_dump` and `pg_restore` are PostgreSQL 18.6, the bundled database's
version. A few tools the Alpine image happened to carry are gone too:
`psql`, `pg_isready`, `pg_dumpall`, `ps`, `top`, `nc`, `unzip` and the
full `wget`. HealthLog uses none of them in the app container; run
`psql` in the `db` container. A health check that
still calls `wget`, from a compose file copied before this release or a
platform template, keeps working: the image carries a small `wget` that
answers health checks and nothing else. The current `docker-compose.yml`
+5 -4
View File
@@ -392,8 +392,9 @@ COPY --from=builder /app/src/generated ./src/generated
# pending migration count, the second runs `migrate deploy` with its
# lock-timeout retry. `db-dump.mjs` takes the dump before migrating, and
# `db-restore.mjs` is the `db-restore` service; both share
# `src/lib/database-backup/dump-core.mjs` with the app. Root-owned on purpose:
# the app user can run them but not rewrite them.
# `src/lib/database-backup/dump-core.mjs` with the app. The files are
# root-owned, so the app user can run them but not edit them in place. The
# directory around them stays the app user's.
COPY --from=builder /app/scripts/reset-password.mjs ./scripts/reset-password.mjs
COPY --from=builder /app/scripts/pending-migrations.mjs ./scripts/pending-migrations.mjs
COPY --from=builder /app/scripts/migrate-deploy.mjs ./scripts/migrate-deploy.mjs
@@ -421,8 +422,8 @@ RUN node scripts/db-dump.mjs 2>&1 | grep -q "db-dump: usage:" && \
pg_restore --version | grep -q "(PostgreSQL) 18\."
# The migration CLI from the `prisma-cli` stage. The Prisma config loads
# dotenv from /app, so expose the isolated copy there. Root-owned: the app
# user can run it but not rewrite it.
# dotenv from /app, so expose the isolated copy there. /opt/prisma-cli is
# root-owned: the app user can run it but not change it.
COPY --from=prisma-cli /opt/prisma-cli /opt/prisma-cli
RUN 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 && \
+3
View File
@@ -62,6 +62,9 @@ function parseArgs(argv) {
};
if (/^https?:\/\//i.test(arg)) {
opts.url ??= arg;
} else if (/^[a-z0-9.[\]:-]+(:\d+)?\//i.test(arg)) {
// busybox wget takes `127.0.0.1:3000/api/health` without a scheme.
opts.url ??= `http://${arg}`;
} else if (arg === "--spider") {
opts.spider = true;
} else if (arg === "-q" || arg === "--quiet") {
+6 -1
View File
@@ -92,7 +92,12 @@ describe("runtime package pins", () => {
pg!.image,
);
expect(client, `a pinned major 18 image, not ${pg!.image}`).not.toBeNull();
const db = /^\s*image:\s*postgres:(\d+\.\d+)-alpine$/m.exec(COMPOSE);
// The `db` service's own image line, not the first postgres image in the
// file: `db-upgrade` carries the previous major.
const db =
/^ {2}db:\n(?:(?! {2}\S).*\n)*? {4}image:\s*postgres:(\d+\.\d+)-alpine$/m.exec(
COMPOSE,
);
expect(db, "the compose file pins the database").not.toBeNull();
// One decision: a database minor bump moves the client with it.
expect(client![1]).toBe(db![1]);
+6
View File
@@ -175,6 +175,12 @@ describe("wget shim", () => {
expect((await wget(["--spider"])).code).toBe(2);
});
it("takes a URL without a scheme, like busybox wget", async () => {
const bare = base.replace(/^http:\/\//, "");
expect((await wget(["-qO-", `${bare}/ok`])).code).toBe(0);
expect((await wget(["-qO-", `${bare}/down`])).code).toBe(1);
});
it("is what the image installs as wget", () => {
const dockerfile = readSource("Dockerfile", "AS runner");
const runner = dockerfile.slice(dockerfile.search(/^FROM .* AS runner$/m));