# ========================================== # Stage 1: Dependencies # Install all dependencies and generate Prisma client # ========================================== FROM node:20-alpine AS dependencies # Install OpenSSL for Prisma support RUN apk add --no-cache openssl libc6-compat WORKDIR /app # Copy package files COPY package*.json ./ # Install all dependencies (including dev dependencies for build) RUN npm ci # Copy Prisma schema and generate client COPY prisma ./prisma RUN npx prisma generate # ========================================== # Stage 2: Builder # Compile TypeScript application # ========================================== FROM node:20-alpine AS builder WORKDIR /app # Copy node_modules from dependencies stage COPY --from=dependencies /app/node_modules ./node_modules # Copy application source COPY . . # Build the application RUN npm run build # Install only production dependencies RUN npm ci --omit=dev && npm cache clean --force # ========================================== # Stage 3: Production Runtime # Minimal runtime image with only necessary files # ========================================== FROM node:20-alpine AS production # Install OpenSSL, dumb-init, and netcat for database health checks RUN apk add --no-cache openssl dumb-init netcat-openbsd # Create non-root user for security RUN addgroup -g 1001 -S nodejs && \ adduser -S nestjs -u 1001 WORKDIR /app # Copy production dependencies from builder COPY --from=builder --chown=nestjs:nodejs /app/node_modules ./node_modules # Copy built application COPY --from=builder --chown=nestjs:nodejs /app/dist ./dist # Copy Prisma schema and migrations (needed for runtime) COPY --from=builder --chown=nestjs:nodejs /app/prisma ./prisma # Copy package.json for metadata COPY --from=builder --chown=nestjs:nodejs /app/package*.json ./ # Copy entrypoint script COPY --chown=nestjs:nodejs docker-entrypoint.sh ./ RUN chmod +x docker-entrypoint.sh # Switch to non-root user USER nestjs # Expose application port EXPOSE 3000 # Health check HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ CMD node -e "require('http').get('http://localhost:3000/api/v1/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})" # Use dumb-init to handle signals properly ENTRYPOINT ["/usr/bin/dumb-init", "--"] # Run entrypoint script (handles migrations then starts app) CMD ["./docker-entrypoint.sh"]