# Multi-stage Dockerfile for React + Express application # See SAAC_DEPLOYMENT.md for deployment rules and common mistakes. # # Stage 1 (builder): Installs client deps and builds the React app with Vite. # Stage 2 (production): Installs server deps only, copies built React output. # # The .dockerignore excludes client/dist from the build context — this is # intentional. The builder stage creates a fresh dist/ inside the container, # and COPY --from=builder copies it between stages (bypasses .dockerignore). # --- Stage 1: Build React frontend --- FROM node:20-alpine AS builder WORKDIR /app/client # Install client dependencies first (layer caching — only re-runs if package*.json change) COPY client/package*.json ./ RUN npm install # Copy client source and build COPY client/ ./ RUN npm run build # --- Stage 2: Production server --- FROM node:20-alpine ARG SOURCE_COMMIT=unknown ARG APP_VERSION=1.0.0 WORKDIR /app # curl is required for the Docker healthcheck below RUN apk add --no-cache curl # Install server dependencies only (not devDependencies — they're not needed in production). # If your app needs devDependencies for a build step (e.g. TypeScript), install ALL deps # first, run the build, then prune: RUN npm install && npm run build && npm prune --production COPY package*.json ./ RUN npm install --production # Copy server code COPY server.js ./ # Copy built React app from the builder stage (not from host — .dockerignore doesn't apply) COPY --from=builder /app/client/dist ./client/dist ENV GIT_COMMIT=${SOURCE_COMMIT} \ APP_VERSION=${APP_VERSION} # Use "expose" in docker-compose.yml, not "ports". This EXPOSE is just documentation. EXPOSE 3000 # Healthcheck — must match the healthcheck in docker-compose.yml HEALTHCHECK --interval=10s --timeout=3s --start-period=5s --retries=2 \ CMD curl -f http://localhost:3000/health || exit 1 CMD ["node", "server.js"]