# Multi-stage build for optimized production image FROM node:18-alpine AS builder # Set working directory WORKDIR /app # Copy package files COPY package.json ./ COPY package-lock.json* ./ COPY tsconfig.json ./ # Install all dependencies (including devDependencies for build) # Use npm ci if package-lock.json exists, otherwise use npm install RUN if [ -f package-lock.json ]; then \ npm ci --legacy-peer-deps; \ else \ npm install --legacy-peer-deps; \ fi # Copy source code COPY src ./src # Build TypeScript to JavaScript RUN npm run build # Production stage FROM node:18-alpine # Set working directory WORKDIR /app # Install dumb-init for proper signal handling RUN apk add --no-cache dumb-init # Copy package files COPY package.json ./ COPY package-lock.json* ./ # Install production dependencies only # Use npm ci if package-lock.json exists, otherwise use npm install RUN if [ -f package-lock.json ]; then \ npm ci --only=production --legacy-peer-deps && npm cache clean --force; \ else \ npm install --only=production --legacy-peer-deps && npm cache clean --force; \ fi # Copy built files from builder stage COPY --from=builder /app/dist ./dist # Copy migration and data files (if needed) COPY migration.sql ./ COPY data ./data # Create non-root user for security RUN addgroup -g 1001 -S nodejs && \ adduser -S nodejs -u 1001 && \ chown -R nodejs:nodejs /app # Switch to non-root user USER nodejs # Expose port (HuggingFace Spaces uses 7860 by default) EXPOSE 7860 # Health check HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ CMD node -e "require('http').get('http://localhost:7860/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})" || exit 1 # Use dumb-init to handle signals properly ENTRYPOINT ["dumb-init", "--"] # Start the application CMD ["node", "dist/index.js"]