# syntax=docker/dockerfile:1 # ---------- Stage 1: build the React/Vite client ---------- FROM node:20-alpine AS client-build WORKDIR /app/client COPY client/package*.json ./ RUN npm ci COPY client/ . RUN npm run build # ---------- Stage 2: install production server deps (native module build) ---------- # Debian slim (glibc) — better-sqlite3 has no glibc prebuilt binary for this # version, so we compile it with build-essential + python3. FROM node:20-slim AS server-deps RUN apt-get update && apt-get install -y --no-install-recommends python3 make g++ \ && rm -rf /var/lib/apt/lists/* WORKDIR /app/server COPY server/package*.json ./ RUN npm ci --omit=dev # ---------- Stage 3: runtime image ---------- FROM node:20-slim RUN apt-get update && apt-get install -y --no-install-recommends tini && rm -rf /var/lib/apt/lists/* WORKDIR /app ENV NODE_ENV=production \ PORT=3001 \ DB_PATH=/app/data/school.db # Server source + production node_modules COPY server/ ./server/ COPY --from=server-deps /app/server/node_modules ./server/node_modules # Built client, served statically by the Express server COPY --from=client-build /app/client/dist ./client/dist COPY docker-entrypoint.sh ./docker-entrypoint.sh RUN chmod +x docker-entrypoint.sh \ && mkdir -p /app/data /app/server/uploads \ && groupadd -g 1001 appgroup && useradd -u 1001 -g appgroup -s /bin/sh -m appuser \ && chown -R appuser:appgroup /app USER appuser EXPOSE 3001 HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \ CMD node -e "require('http').get('http://localhost:3001/api/health',r=>{process.exit(r.statusCode===200?0:1)}).on('error',()=>process.exit(1))" ENTRYPOINT ["/usr/bin/tini", "--"] CMD ["./docker-entrypoint.sh"]