Skip to content

Docker best-practices audit: 0 high / 4 med / 2 low (Bret Fisher rubric) #275

Description

@TortoiseWolfe

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_STANDALONEnext.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_STANDALONEdocker/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_HANDLINGdocker/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 · SECRETSdocker-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_IMAGEdocker/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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions