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
This commit is contained in:
Marc-André Bombeck committed 2026-09-16 12:19:09 +02:00
1 parent 0ec05c3c5a
commit 79c4c3631f
3 files changed
+68 -1

No files matched your search

+9
View File
@@ -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
+10 -1
View File
@@ -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
@@ -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");
});
});