| # --- Stage 1: Build frontend --- | |
| FROM node:20-slim AS frontend-build | |
| WORKDIR /app/frontend | |
| COPY frontend/package.json frontend/package-lock.json* frontend/.npmrc* ./ | |
| RUN npm install | |
| COPY frontend/ ./ | |
| COPY backend/src/shared/ ../backend/src/shared/ | |
| # The shared files import npm packages (e.g. `shiki`) that are declared in the | |
| # frontend package.json. When rollup resolves those imports from /app/backend/ | |
| # it walks up the tree looking for node_modules; without this symlink it never | |
| # reaches /app/frontend/node_modules and the build fails with | |
| # "Rollup failed to resolve import 'shiki'". | |
| RUN ln -s /app/frontend/node_modules /app/backend/node_modules | |
| RUN npm run build | |
| # --- Stage 2: Build backend --- | |
| FROM node:20-slim AS backend-build | |
| WORKDIR /app | |
| COPY backend/package.json backend/package-lock.json* ./ | |
| RUN npm install | |
| COPY backend/ ./ | |
| RUN npx tsc | |
| # --- Stage 3: Production image --- | |
| FROM node:20-slim | |
| RUN apt-get update && apt-get install -y --no-install-recommends \ | |
| libglib2.0-0 \ | |
| libnss3 \ | |
| libnspr4 \ | |
| libatk1.0-0 \ | |
| libatk-bridge2.0-0 \ | |
| libcups2 \ | |
| libdrm2 \ | |
| libdbus-1-3 \ | |
| libxkbcommon0 \ | |
| libatspi2.0-0 \ | |
| libxcomposite1 \ | |
| libxdamage1 \ | |
| libxfixes3 \ | |
| libxrandr2 \ | |
| libgbm1 \ | |
| libpango-1.0-0 \ | |
| libcairo2 \ | |
| libasound2 \ | |
| libwayland-client0 \ | |
| fonts-noto-color-emoji \ | |
| fonts-liberation \ | |
| && rm -rf /var/lib/apt/lists/* | |
| WORKDIR /app | |
| # Copy compiled backend + prod dependencies | |
| COPY backend/package.json backend/package-lock.json* ./ | |
| RUN npm install --omit=dev | |
| COPY --from=backend-build /app/dist ./dist | |
| # Install Playwright Chromium browser binaries. | |
| # We invoke the playwright CLI through its package path instead of `npx | |
| # playwright` because the `.bin/playwright` symlink clashes with the one | |
| # shipped by @playwright/test (dev-only) and isn't recreated when npm is run | |
| # with --omit=dev. | |
| ENV PLAYWRIGHT_BROWSERS_PATH=/app/browsers | |
| RUN node node_modules/playwright/cli.js install chromium | |
| # Copy frontend build | |
| COPY --from=frontend-build /app/frontend/dist ./frontend-dist | |
| # Copy CSS source files for the HTML renderer | |
| COPY frontend/src/styles/ ./frontend-styles/ | |
| ENV PORT=8080 | |
| ENV NODE_ENV=production | |
| ENV ENABLE_PDF=true | |
| EXPOSE 8080 | |
| CMD ["node", "dist/server.js"] | |