# syntax=docker/dockerfile:1.7 # # FORGE — multi-stage build. Two sub-builds feed one slim runtime: # - client (Vite/React) -> client/dist (static assets) # - server (tsc) -> dist/index.js (Express API + static server) # # Base pinned to a specific patch; bump deliberately. The runtime carries only # prod deps (`--omit=dev`) + the two build outputs, and runs as the non-root # `node` user. FROM node:20.19.6-alpine3.21 AS base # --- Stage 1: client build -------------------------------------------------- FROM base AS client-build WORKDIR /build/client COPY client/package.json client/package-lock.json* ./ RUN npm install COPY client/ ./ RUN npm test RUN npm run build # --- Stage 2: server build -------------------------------------------------- FROM base AS server-build WORKDIR /build COPY package.json package-lock.json* ./ RUN npm install COPY tsconfig.json ./ COPY index.ts ./ COPY server/ ./server/ RUN npx tsc # --- Stage 3: prod deps ----------------------------------------------------- FROM base AS prod-deps WORKDIR /app COPY package.json package-lock.json* ./ RUN npm install --omit=dev # --- Stage 4: runtime ------------------------------------------------------- FROM base AS runtime WORKDIR /app ENV NODE_ENV=production \ PORT=3000 COPY --from=prod-deps --chown=node:node /app/node_modules ./node_modules COPY --from=server-build --chown=node:node /build/dist ./dist COPY --from=server-build --chown=node:node /build/server/data ./dist/server/data COPY --from=client-build --chown=node:node /build/client/dist ./client/dist COPY --chown=node:node package.json ./ USER node EXPOSE 3000 HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \ CMD wget --spider -q http://localhost:3000/healthz || exit 1 CMD ["node", "dist/index.js"]