diff --git a/.changeset/postgres-18-volume-mount.md b/.changeset/postgres-18-volume-mount.md new file mode 100644 index 0000000..4a814f2 --- /dev/null +++ b/.changeset/postgres-18-volume-mount.md @@ -0,0 +1,22 @@ +--- +"seamless-cli": patch +--- + +Fix the scaffolded database failing to start on PostgreSQL 18. + +The PostgreSQL 18 bump moved the image tag but not the volume mount. PostgreSQL 18+ images store data +in a major-versioned subdirectory (`/var/lib/postgresql/18/docker`), so a mount at +`/var/lib/postgresql/data` is ignored and the container refuses to start, restart-looping on: + +``` +Error: in 18+, these Docker images are configured to store database data in a + format which is compatible with "pg_ctlcluster" ... + Counter to that, there appears to be PostgreSQL data in: + /var/lib/postgresql/data (unused mount/volume) +``` + +The generated `docker-compose.yml` now mounts `pgdata:/var/lib/postgresql`. See +docker-library/postgres#1259. + +This only ever affected projects scaffolded from the unreleased PostgreSQL 18 change, so no published +version of the CLI produced a broken scaffold. diff --git a/src/generators/docker/docker.test.ts b/src/generators/docker/docker.test.ts index 3719b7b..961d551 100644 --- a/src/generators/docker/docker.test.ts +++ b/src/generators/docker/docker.test.ts @@ -226,6 +226,28 @@ describe("generateDockerCompose", () => { expect(compose.endsWith("\n")).toBe(true); }); + // PostgreSQL 18+ images keep data in a major-versioned subdirectory, so a mount + // at .../data is ignored and the container restart-loops on + // "in 18+, these Docker images are configured to store database data in a + // format which is compatible with pg_ctlcluster". The scaffold shipped exactly + // that for one release cycle. + it("mounts the database volume where PostgreSQL 18 actually stores data", async () => { + writeAuthEnvFixture(tmpDir, "SEAMLESS_JWKS_ACTIVE_KID"); + + await generateDockerCompose(tmpDir, { + authMode: "local", + adminMode: "image", + }); + + const compose = fs.readFileSync( + path.join(tmpDir, "docker-compose.yml"), + "utf-8", + ); + + expect(compose).toContain("- pgdata:/var/lib/postgresql\n"); + expect(compose).not.toContain("/var/lib/postgresql/data"); + }); + // Publishing on 0.0.0.0 put the auth server on the LAN, and it is configured to // hand back OTP codes in the response for local login. it("publishes every port on loopback only", async () => { diff --git a/src/generators/docker/docker.ts b/src/generators/docker/docker.ts index 87c47bb..65b81ee 100644 --- a/src/generators/docker/docker.ts +++ b/src/generators/docker/docker.ts @@ -72,7 +72,11 @@ services: POSTGRES_PASSWORD: mypassword POSTGRES_DB: postgres volumes: - - pgdata:/var/lib/postgresql/data + # PostgreSQL 18+ images store data in a major-versioned subdirectory + # (/var/lib/postgresql/18/docker), so the mount goes one level up. Mounting + # .../data instead leaves the volume unused and the container refuses to + # start. See docker-library/postgres#1259. + - pgdata:/var/lib/postgresql healthcheck: test: ["CMD-SHELL", "pg_isready -U myuser -d postgres"] interval: 5s