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 password hashing and the Prisma engine run their glibc builds. The
published arm64 image renders PDF pages on a Raspberry Pi 4 again, where 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 the Alpine build of the renderer needs CPU features an ARMv8.0 core
lacks. Node security releases now reach the image with the official lacks. Node security releases now come with the official Node image,
Node image, usually within days. The image carries no package manager: which HealthLog pins and moves forward with its releases. The image
npm, npx, corepack and yarn are removed. `pg_dump` and `pg_restore` are carries no package manager: npm, npx, corepack and yarn are removed.
PostgreSQL 18.6, the bundled database's version; `psql` is no longer in `pg_dump` and `pg_restore` are PostgreSQL 18.6, the bundled database's
the app image, so run it in the `db` container. A health check that 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 still calls `wget`, from a compose file copied before this release or a
platform template, keeps working: the image carries a small `wget` that platform template, keeps working: the image carries a small `wget` that
answers health checks and nothing else. The current `docker-compose.yml` 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 # pending migration count, the second runs `migrate deploy` with its
# lock-timeout retry. `db-dump.mjs` takes the dump before migrating, and # lock-timeout retry. `db-dump.mjs` takes the dump before migrating, and
# `db-restore.mjs` is the `db-restore` service; both share # `db-restore.mjs` is the `db-restore` service; both share
# `src/lib/database-backup/dump-core.mjs` with the app. Root-owned on purpose: # `src/lib/database-backup/dump-core.mjs` with the app. The files are
# the app user can run them but not rewrite them. # 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/reset-password.mjs ./scripts/reset-password.mjs
COPY --from=builder /app/scripts/pending-migrations.mjs ./scripts/pending-migrations.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 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\." pg_restore --version | grep -q "(PostgreSQL) 18\."
# The migration CLI from the `prisma-cli` stage. The Prisma config loads # 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 # dotenv from /app, so expose the isolated copy there. /opt/prisma-cli is
# user can run it but not rewrite it. # root-owned: the app user can run it but not change it.
COPY --from=prisma-cli /opt/prisma-cli /opt/prisma-cli 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 && \ 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 && \ 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)) { if (/^https?:\/\//i.test(arg)) {
opts.url ??= 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") { } else if (arg === "--spider") {
opts.spider = true; opts.spider = true;
} else if (arg === "-q" || arg === "--quiet") { } else if (arg === "-q" || arg === "--quiet") {
+6 -1
View File
@@ -92,7 +92,12 @@ describe("runtime package pins", () => {
pg!.image, pg!.image,
); );
expect(client, `a pinned major 18 image, not ${pg!.image}`).not.toBeNull(); 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(); expect(db, "the compose file pins the database").not.toBeNull();
// One decision: a database minor bump moves the client with it. // One decision: a database minor bump moves the client with it.
expect(client![1]).toBe(db![1]); expect(client![1]).toBe(db![1]);
+6
View File
@@ -175,6 +175,12 @@ describe("wget shim", () => {
expect((await wget(["--spider"])).code).toBe(2); 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", () => { it("is what the image installs as wget", () => {
const dockerfile = readSource("Dockerfile", "AS runner"); const dockerfile = readSource("Dockerfile", "AS runner");
const runner = dockerfile.slice(dockerfile.search(/^FROM .* AS runner$/m)); const runner = dockerfile.slice(dockerfile.search(/^FROM .* AS runner$/m));