# Stage 1: Build client FROM node:20-alpine AS client-build WORKDIR /app/client COPY client/package.json ./ RUN npm install COPY client/ ./ RUN npm run build # Stage 2: Build server FROM node:20-alpine AS server-build WORKDIR /app/server COPY server/package.json ./ RUN npm install COPY server/ ./ RUN npm run build # Stage 3: Production FROM node:20-alpine AS production RUN apk add --no-cache tini curl WORKDIR /app # Create non-root user RUN addgroup -g 1001 -S appgroup && \ adduser -S appuser -u 1001 -G appgroup # Copy server production dependencies COPY server/package.json ./ RUN npm install --omit=dev && npm cache clean --force # Copy server build output COPY --from=server-build /app/server/dist ./dist # Copy client build output to be served as static files COPY --from=client-build /app/client/dist ./public # Create data directory for uploads RUN mkdir -p /data/uploads && chown -R appuser:appgroup /data/uploads # Switch to non-root user USER appuser EXPOSE 3000 # Health check HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD curl -f http://localhost:3000/api/health || exit 1 ENTRYPOINT ["/sbin/tini", "--"] CMD ["node", "dist/index.js"]