# ─── Stage 1: Build ─────────────────────────────────────────────────────── FROM node:20-alpine AS builder WORKDIR /app # Server COPY server/package*.json ./server/ RUN cd server && npm ci COPY server/ ./server/ RUN cd server && npm run db:generate && npm run build # Client COPY client/package*.json ./client/ RUN cd client && npm ci COPY client/ ./client/ RUN cd client && npm run build # ─── Stage 2: Runtime ───────────────────────────────────────────────────── FROM node:20-alpine AS runtime # Security: run as non-root RUN addgroup -S appgroup && adduser -S appuser -G appgroup WORKDIR /app ENV NODE_ENV=production # Server production deps only COPY server/package*.json ./server/ RUN cd server && npm ci --omit=dev && npm cache clean --force # Built artifacts COPY --from=builder /app/server/dist ./server/dist COPY --from=builder /app/server/prisma ./server/prisma COPY --from=builder /app/server/node_modules/.prisma ./server/node_modules/.prisma COPY --from=builder /app/server/node_modules/@prisma ./server/node_modules/@prisma # React SPA COPY --from=builder /app/client/dist ./client/dist # Data directory for SQLite (bind-mount or volume in production) RUN mkdir -p /data && chown appuser:appgroup /data USER appuser EXPOSE 8080 HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \ CMD wget -qO- http://localhost:8080/api/v1/health || exit 1 WORKDIR /app/server CMD ["sh", "-c", "npx prisma migrate deploy && node dist/index.js"]