Docker best-practices audit — ScriptHammer
Audited against Bret Fisher's Node-in-Docker rules (workspace copy: TranScripts/Docker/Docker_Edited/DOCKER_SYSTEM_PROMPT.md), 2026-07-12. Read-only audit; every item cited at file:line.
Verdict: Node/Next.js PWA with a genuine multi-stage Dockerfile that follows most Bret Fisher patterns (node:22-slim, USER node/nextjs, --chown, node server.js, init:true in dev compose, healthchecks with condition:service_healthy). The one real defect is a broken production target: next.config.ts uses output:'export' (static, no server) while the runner stage copies .next/standalone and runs node server.js — a config/Dockerfile mismatch that makes the prod image non-functional and violates the Next.js standalone rule.
Findings
- ⚠️ MED · NEXTJS_STANDALONE —
next.config.ts:43
- Evidence:
output: 'export',
- Fix: The prod runner stage copies .next/standalone and runs node server.js, but the config is static-export (no standalone/server.js is ever emitted); either set output:'standalone' to match the runner stage, or delete the dead runner stage since deployment is static export to GitHub Pages.
- ⚠️ MED · NEXTJS_STANDALONE —
docker/Dockerfile:173
- Evidence:
COPY --from=builder /app/.next/standalone ./
- Fix: With output:'export' this path never exists so the build breaks at this COPY; switch next.config.ts to output:'standalone' or remove the runner stage (lines 162-183).
- ⚠️ MED · SIGNAL_HANDLING —
docker/Dockerfile:183
- Evidence:
CMD ["node", "server.js"]
- Fix: The runner (prod) stage runs bare node as PID1 with no tini/ENTRYPOINT init and no visible SIGTERM handler; add ENTRYPOINT ["tini","--"] (or
docker run --init) for the prod image — the dev compose sets init:true but the runner stage does not.
- ⚠️ MED · SECRETS —
docker-compose.yml:22-25
- Evidence:
JWT_SECRET: ${SUPABASE_JWT_SECRET:-<demo-secret-redacted>}
- Fix: Real (if public/demo) JWT secret + anon/service-role keys + postgres password are committed as inline defaults; move them to a Compose
secrets: block or an untracked .env even for the documented demo values so the pattern isn't copied into a deployed stack. (these are the public Supabase self-host DEMO keys, documented as local-only — not a real leak; move to compose secrets so the pattern isn't copied downstream.)
- 🔹 LOW · DOCKERIGNORE_PRESENT —
.dockerignore:46-49
- Evidence:
Dockerfile docker-compose.yml .dockerignore
- Fix: Ignore list misses docker/Dockerfile.e2e, docker/docker-compose.e2e.yml, tests/, and playwright-report/ — broaden to Dockerfile*, docker/, and test/coverage dirs so the build context stays lean.
- 🔹 LOW · BASE_IMAGE —
docker/Dockerfile.e2e:2
- Evidence:
FROM mcr.microsoft.com/playwright:v1.48.0-focal
- Fix: Test-only image (not scored as prod) but pinned to Ubuntu 20.04 focal which is aging; bump to a jammy/noble-based Playwright tag when convenient — no prod impact.
Already compliant: BASE_IMAGE, NON_ROOT, CHOWN, PROD_DEPS, MULTISTAGE_PROD, CMD_NOT_NPM_START, ONE_IMAGE_RUNTIME_CONFIG, DOCKERIGNORE_PRESENT, NO_VERSION_KEY, HEALTHCHECK_READINESS, DEV_WATCH_OR_SANE_MOUNTS, PORTS_MINIMAL
Auditor notes
Next.js 15 PWA, pnpm@10.16.1, React 19. THE HEADLINE FINDING: the Dockerfile's prod runner stage is well-formed on paper (multi-stage, USER nextjs, NODE_ENV=production, CMD ["node","server.js"]) but is DEAD/BROKEN because next.config.ts:43 sets output:'export' (static HTML export to out/), which never emits .next/standalone or server.js. The runner stage copies .next/standalone (line 173) and runs node server.js (line 183) — artifacts this config cannot produce, so docker build --target runner fails at the COPY or the container instantly exits "Cannot find module server.js". Real deployment is static export to GitHub Pages (per CLAUDE.md: "GitHub Pages, static only, no server API routes"), so there is genuinely no running Node prod server. I scored the runner stage against prod rules as written; most prod structural rules PASS in form, but NEXTJS_STANDALONE is a real violation (config/Dockerfile mismatch) and SIGNAL_HANDLING leans violation for the runner (bare node PID1, no tini/init in that stage — the dev compose has init:true but the runner image and the e2e/compose paths for prod don't). The e2e Dockerfile (Dockerfile.e2e) is a TEST image (Playwright focal base, npm start-style CMD, npm i -g pnpm@8) — not scored against prod rules, but its pnpm run dev & background CMD and pnpm@8 pin are dev/test smells, not prod violations. Committed Supabase demo JWT/keys in docker-compose.yml are the documented public self-hosting demo values (LOCAL DEV ONLY per the file comment), gated behind the supabase profile — flagged MED for hygiene, not a leaked real secret.
Docker best-practices audit — ScriptHammer
Audited against Bret Fisher's Node-in-Docker rules (workspace copy:
TranScripts/Docker/Docker_Edited/DOCKER_SYSTEM_PROMPT.md), 2026-07-12. Read-only audit; every item cited at file:line.Verdict: Node/Next.js PWA with a genuine multi-stage Dockerfile that follows most Bret Fisher patterns (node:22-slim, USER node/nextjs, --chown, node server.js, init:true in dev compose, healthchecks with condition:service_healthy). The one real defect is a broken production target: next.config.ts uses output:'export' (static, no server) while the runner stage copies .next/standalone and runs node server.js — a config/Dockerfile mismatch that makes the prod image non-functional and violates the Next.js standalone rule.
Findings
next.config.ts:43output: 'export',docker/Dockerfile:173COPY --from=builder /app/.next/standalone ./docker/Dockerfile:183CMD ["node", "server.js"]docker run --init) for the prod image — the dev compose sets init:true but the runner stage does not.docker-compose.yml:22-25JWT_SECRET: ${SUPABASE_JWT_SECRET:-<demo-secret-redacted>}secrets:block or an untracked .env even for the documented demo values so the pattern isn't copied into a deployed stack. (these are the public Supabase self-host DEMO keys, documented as local-only — not a real leak; move to compose secrets so the pattern isn't copied downstream.).dockerignore:46-49Dockerfile docker-compose.yml .dockerignoredocker/Dockerfile.e2e:2FROM mcr.microsoft.com/playwright:v1.48.0-focalAlready compliant: BASE_IMAGE, NON_ROOT, CHOWN, PROD_DEPS, MULTISTAGE_PROD, CMD_NOT_NPM_START, ONE_IMAGE_RUNTIME_CONFIG, DOCKERIGNORE_PRESENT, NO_VERSION_KEY, HEALTHCHECK_READINESS, DEV_WATCH_OR_SANE_MOUNTS, PORTS_MINIMAL
Auditor notes
Next.js 15 PWA, pnpm@10.16.1, React 19. THE HEADLINE FINDING: the Dockerfile's prod
runnerstage is well-formed on paper (multi-stage, USER nextjs, NODE_ENV=production, CMD ["node","server.js"]) but is DEAD/BROKEN because next.config.ts:43 sets output:'export' (static HTML export to out/), which never emits .next/standalone or server.js. The runner stage copies .next/standalone (line 173) and runs node server.js (line 183) — artifacts this config cannot produce, sodocker build --target runnerfails at the COPY or the container instantly exits "Cannot find module server.js". Real deployment is static export to GitHub Pages (per CLAUDE.md: "GitHub Pages, static only, no server API routes"), so there is genuinely no running Node prod server. I scored the runner stage against prod rules as written; most prod structural rules PASS in form, but NEXTJS_STANDALONE is a real violation (config/Dockerfile mismatch) and SIGNAL_HANDLING leans violation for the runner (bare node PID1, no tini/init in that stage — the dev compose has init:true but the runner image and the e2e/compose paths for prod don't). The e2e Dockerfile (Dockerfile.e2e) is a TEST image (Playwright focal base, npm start-style CMD, npm i -g pnpm@8) — not scored against prod rules, but itspnpm run dev &background CMD and pnpm@8 pin are dev/test smells, not prod violations. Committed Supabase demo JWT/keys in docker-compose.yml are the documented public self-hosting demo values (LOCAL DEV ONLY per the file comment), gated behind thesupabaseprofile — flagged MED for hygiene, not a leaked real secret.