Single-image multistage Dockerfile: bundled frontend + backend, tenant-portable
Altien replaces the upstream backend-only Dockerfile with a three-stage build that produces one image serving both the compiled Express API and the Next.js static export. The `NEXT_PUBLIC_API_BASE_URL` build arg handles same-origin vs split-origin deployments from a single Dockerfile.
The build has three stages. frontend-builder (node:22-slim) runs npm run build against frontend/, producing a static export to /app/out. NEXT_PUBLIC_API_BASE_URL is the only build-time arg the frontend needs - empty means same-origin, which is the bundled-deploy case where Express serves both API and frontend. Everything else (auth provider, Entra IDs) is fetched from GET /config at browser startup, so the image doesn't need to be rebuilt per tenant. backend-builder compiles TypeScript to /app/dist. The runtime stage is node:22-slim with LibreOffice installed for DOCX-to-PDF conversion, production node_modules, the compiled backend, the frontend export at /app/public, migration SQL files, and the scripts/install/ directory.
Express picks up /app/public via express.static when the directory exists - local backend dev doesn't have it and falls through to CORS-based separation.
frontend/next.config.ts flips to output: "export". The --legacy-peer-deps flag in the frontend npm ci step is explained in a comment: @opennextjs/cloudflare left over from a previous deploy-target experiment peer-depends on an older Next.js version. The comment says it should be removed in a later cleanup.
A follow-up commit (feec6d3e) adds scripts/install/README.md as a placeholder. The Dockerfile's runtime stage unconditionally COPY scripts/install ./scripts/install, but the OSS fork doesn't ship those scripts. Without the placeholder, docker build and az acr build fail with "COPY failed: file not found in build context". The install route uses fs.existsSync and degrades cleanly when the directory is empty.
Spotted something wrong? Or know the PR text has fresher detail than the writeup above?