35 lines
1.7 KiB
Docker
35 lines
1.7 KiB
Docker
# ---------- Build stage ----------
|
|
# glibc (Debian) base: the lockfile's native binaries (@rollup/rollup-linux-x64-gnu,
|
|
# @esbuild/linux-x64) are built against glibc. alpine uses musl and would fail to load them.
|
|
FROM node:22-slim AS build
|
|
WORKDIR /app
|
|
|
|
# Copy lockfile + package manifests first so this layer is cached when only source changes.
|
|
COPY package.json package-lock.json ./
|
|
COPY apps/web/package.json ./apps/web/package.json
|
|
|
|
# SINGLE install. --include=optional brings the platform-native Rollup/esbuild binaries
|
|
# Vite needs. Never add a second `npm ci --workspace=apps/web` here: it reinstalls node_modules
|
|
# in a way that drops `vite` from the bin path, producing `sh: 1: vite: not found` (exit 127).
|
|
# Never use --no-optional / --omit=optional either: it omits @rollup/rollup-linux-x64-gnu
|
|
# and the Vite build crashes with "Cannot find module @rollup/rollup-linux-x64-gnu".
|
|
RUN npm ci --include=optional --no-audit --no-fund
|
|
|
|
# Copy the app source only (keeps the build context small; .dockerignore excludes node_modules).
|
|
COPY apps/web ./apps/web
|
|
|
|
# Build the static site. We call vite directly via its installed binary instead of the
|
|
# `npm run build` workspace script, so the build never depends on npm's script PATH resolution.
|
|
# generate-llms.js is optional SEO scaffolding (`|| true`); vite build is the real step.
|
|
RUN cd apps/web \
|
|
&& (node tools/generate-llms.js || true) \
|
|
&& node ../../node_modules/vite/bin/vite.js build --outDir ../../dist/apps/web
|
|
|
|
# ---------- Production stage ----------
|
|
# Pure static hosting — no Node.js runtime in the final image.
|
|
FROM nginx:stable-alpine
|
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
|
COPY --from=build /app/dist/apps/web /usr/share/nginx/html
|
|
EXPOSE 80
|
|
CMD ["nginx", "-g", "daemon off;"]
|