diff --git a/apps/server/Dockerfile b/apps/server/Dockerfile index 733c9b7..3b2c97c 100644 --- a/apps/server/Dockerfile +++ b/apps/server/Dockerfile @@ -3,15 +3,19 @@ # @personal-blog/server — EPPP public server application image. # # [E00-S02-T01] baseline: builds the workspace server package with the pinned -# toolchain (Node 24 + pnpm 11.23.0, frozen lockfile) and runs the compiled +# toolchain (Node 24.19.0 + pnpm 11.23.0, frozen lockfile) and runs the compiled # entrypoint. The server is still a bootstrap placeholder (its module loads and # exits cleanly); the Fastify 5 application shell that turns it into a serving # process lands in a later story, and the health gate (T02), health endpoint # (T03), volume persistence (T04), non-root/read-only hardening and multi-arch # targets are later E00-S02 tasks — all out of scope here. +# +# Image base: node:24.19.0-bookworm-slim (glibc Debian) per Technology-Stack +# §5.4 — argon2 is a native dependency and musl/Alpine causes native-module +# build surprises, so the image must stay on a glibc base. # --- build stage: install the frozen workspace and compile the server -------- -FROM node:24-alpine AS build +FROM node:24.19.0-bookworm-slim AS build WORKDIR /app # Enable the pinned pnpm (11.23.0, via packageManager in the root package.json) @@ -33,8 +37,8 @@ RUN pnpm install --frozen-lockfile COPY apps/server apps/server RUN pnpm --filter @personal-blog/server build -# --- runtime stage: Node 24 + compiled output only ---------------------------- -FROM node:24-alpine AS runtime +# --- runtime stage: Node 24.19.0 (bookworm-slim) + compiled output only ------ +FROM node:24.19.0-bookworm-slim AS runtime WORKDIR /app ENV NODE_ENV=production diff --git a/compose.yaml b/compose.yaml index b4a3d3e..327c055 100644 --- a/compose.yaml +++ b/compose.yaml @@ -13,7 +13,9 @@ services: db: - image: postgres:16-alpine + # PostgreSQL 18 on Debian bookworm — the documented runtime target + # (Technology-Stack §5.2/§5.4/§6.2, golden tuple §7; no Alpine drift). + image: postgres:18-bookworm environment: POSTGRES_DB: ${POSTGRES_DB:-eppp} POSTGRES_USER: ${POSTGRES_USER:-eppp} diff --git a/tests/compose-config.test.mjs b/tests/compose-config.test.mjs index 55e6c63..10f8cc9 100644 --- a/tests/compose-config.test.mjs +++ b/tests/compose-config.test.mjs @@ -13,8 +13,9 @@ * container is up. * - "docker compose up -d starts the application" → the committed * `compose.yaml` declares an `app` service built from the committed - * `apps/server/Dockerfile` (multi-stage: Node 24 + frozen pnpm install → - * `tsc` build of `@personal-blog/server` → `node apps/server/dist/index.js`), + * `apps/server/Dockerfile` (multi-stage: Node 24.19.0 bookworm-slim + + * frozen pnpm install → `tsc` build of `@personal-blog/server` → + * `node apps/server/dist/index.js`), * with a published default port and `depends_on: db` so the application * starts after the database. The real-stack probe asserts the `app` * container is created and starts cleanly (exit 0 when the placeholder @@ -158,8 +159,8 @@ function assertDbService(compose) { assert.ok(db, 'compose.yaml must declare a "db" service (docker compose up -d starts the database)'); assert.equal( db.image, - 'postgres:16-alpine', - 'the "db" service must use the committed PostgreSQL image (postgres:16-alpine)', + 'postgres:18-bookworm', + 'the "db" service must use the committed PostgreSQL 18 image (postgres:18-bookworm)', ); const env = db.environment ?? {}; assert.equal(env.POSTGRES_DB, '${POSTGRES_DB:-eppp}', 'db must set POSTGRES_DB (with a default)'); @@ -291,13 +292,13 @@ test('the application image is defined by a committed multi-stage Dockerfile', ( const dockerfile = read(DOCKERFILE_PATH); assert.match( dockerfile, - /FROM node:24-alpine AS build/, - 'the Dockerfile must build on the committed Node 24 line (FROM node:24-alpine AS build)', + /FROM node:24\.19\.0-bookworm-slim AS build/, + 'the Dockerfile must build on the committed Node 24.19.0 bookworm-slim base (FROM node:24.19.0-bookworm-slim AS build)', ); assert.match( dockerfile, - /FROM node:24-alpine AS runtime/, - 'the Dockerfile must ship a slim Node 24 runtime stage (FROM node:24-alpine AS runtime)', + /FROM node:24\.19\.0-bookworm-slim AS runtime/, + 'the Dockerfile must ship a Node 24.19.0 bookworm-slim runtime stage (FROM node:24.19.0-bookworm-slim AS runtime)', ); assert.match( dockerfile, @@ -394,7 +395,7 @@ test('the YAML parser reads the committed structure (non-vacuous parser probe)', const parsed = parseYaml(` services: db: - image: postgres:16-alpine + image: postgres:18-bookworm environment: POSTGRES_DB: eppp ports: @@ -406,7 +407,7 @@ services: depends_on: - db `); - assert.equal(parsed.services.db.image, 'postgres:16-alpine'); + assert.equal(parsed.services.db.image, 'postgres:18-bookworm'); assert.equal(parsed.services.db.environment.POSTGRES_DB, 'eppp'); assert.deepEqual(parsed.services.db.ports, ['5432:5432']); assert.equal(parsed.services.app.build.dockerfile, 'apps/server/Dockerfile');