From 79c4c3631f0be32c7488ef3b28efcddba3ae29f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Wed, 16 Sep 2026 12:19:09 +0200 Subject: [PATCH] chore(deps): move the database pin to 16.15 and watch it The bundled PostgreSQL was pinned to 16.14 while 16.15 had been out for five weeks. The pin itself is deliberate, a database change should be reviewable rather than whatever the registry serves on pull day, but nothing watched it: Dependabot was configured with the docker ecosystem, which reads the Dockerfile and never opens a compose file, so no update PR was ever raised. CI and the integration suite meanwhile run against the floating 16-alpine, so the shipped database was older than the tested one. Adds the docker-compose ecosystem so each PostgreSQL minor release arrives as a reviewable PR, moves the pin to 16.15, and pins both halves with a test that fails if the exact minor or the watch is dropped. Refs #987 --- .github/dependabot.yml | 9 ++++ docker-compose.yml | 11 ++++- src/__tests__/database-pin-is-watched.test.ts | 49 +++++++++++++++++++ 3 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 src/__tests__/database-pin-is-watched.test.ts diff --git a/.github/dependabot.yml b/.github/dependabot.yml index f8a01fce5..46efc732f 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -29,3 +29,12 @@ updates: schedule: interval: "weekly" open-pull-requests-limit: 3 + # 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 + # reaches us and the pin ages silently. + - package-ecosystem: "docker-compose" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 3 diff --git a/docker-compose.yml b/docker-compose.yml index fd8815206..4e0897edb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -482,7 +482,16 @@ services: # existing deployments see no image change). Minor releases share # the on-disk format — bumping this pin is safe without a dump / # restore; a major-version bump (17+) is not. - image: postgres:16.14-alpine + # + # The pin only works as long as something watches it: PostgreSQL + # publishes minor releases with security and data-corruption fixes and + # recommends running the current one, and this line sat two minors + # behind for five weeks because the Dockerfile was the only thing + # Dependabot looked at. The `docker-compose` ecosystem in + # `.github/dependabot.yml` now raises a PR for each new minor; + # `src/__tests__/database-pin-is-watched.test.ts` keeps both halves + # (concrete pin, active watch) from being dropped. + image: postgres:16.15-alpine container_name: healthlog-db # cap_drop: [ALL] is deliberately NOT applied here (unlike `app`): the # official Postgres entrypoint starts as root, chowns the data diff --git a/src/__tests__/database-pin-is-watched.test.ts b/src/__tests__/database-pin-is-watched.test.ts new file mode 100644 index 000000000..2ab74ceff --- /dev/null +++ b/src/__tests__/database-pin-is-watched.test.ts @@ -0,0 +1,49 @@ +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; + +import { describe, expect, it } from "vitest"; + +/** + * The bundled database is pinned to an exact minor, AND something watches it. + * + * Both halves matter and only together. The pin exists so a database change is + * a deliberate, reviewable one rather than whatever the registry happens to + * serve on the day an operator pulls. But PostgreSQL ships security and + * data-corruption fixes in minor releases and recommends running the current + * one, so a pin nobody moves ships a knowingly outdated database: this line + * sat on 16.14 for five weeks after 16.15 was published, because the + * `docker` ecosystem Dependabot was configured with reads the Dockerfile and + * never looks at a compose file. + * + * So: drop the pin and the deliberate-update model goes; drop the watch and + * the pin ages in silence. This test fails on either. + */ + +const ROOT = resolve(__dirname, "../.."); + +function read(relative: string): string { + return readFileSync(resolve(ROOT, relative), "utf8"); +} + +describe("the bundled database pin", () => { + it("names an exact minor version, not a floating major alias", () => { + const images = [ + ...read("docker-compose.yml").matchAll(/^\s*image:\s*(postgres:\S+)/gm), + ].map((match) => match[1]); + + expect(images.length).toBe(1); + // `postgres:16-alpine` or `postgres:16` would match a moving target. + expect(images[0]).toMatch(/^postgres:\d+\.\d+-alpine$/); + }); + + it("is watched by the compose ecosystem, which is not the Dockerfile one", () => { + const dependabot = read(".github/dependabot.yml"); + const ecosystems = [ + ...dependabot.matchAll(/package-ecosystem:\s*"([^"]+)"/g), + ].map((match) => match[1]); + + expect(ecosystems).toContain("docker-compose"); + // The Dockerfile watch is a separate entry and does not stand in for it. + expect(ecosystems).toContain("docker"); + }); +});