# ============================================================ # STAGE 1: Build the React/Vite Frontend # ============================================================ FROM node:20-alpine AS frontend-build WORKDIR /app/frontend # Copy package files and install dependencies COPY F_Pro/package.json F_Pro/package-lock.json ./ RUN npm ci # Copy the rest of the frontend source COPY F_Pro/ . # Build-time env vars that Vite bakes into the static bundle. # Pass them with --build-arg in `docker build` or set them # as HuggingFace Space secrets (they become ARGs automatically). ARG VITE_GOOGLE_CLIENT_ID ARG VITE_STRIPE_PUBLISHABLE_KEY # For HuggingFace the app is served on port 7860, so the API # lives on the same host. Override these if you use an external backend. ARG VITE_API_BASE_URL=http://localhost:7860/api ARG VITE_WS_URL=ws://localhost:7860/ws ENV VITE_GOOGLE_CLIENT_ID=$VITE_GOOGLE_CLIENT_ID ENV VITE_STRIPE_PUBLISHABLE_KEY=$VITE_STRIPE_PUBLISHABLE_KEY ENV VITE_API_BASE_URL=$VITE_API_BASE_URL ENV VITE_WS_URL=$VITE_WS_URL RUN npm run build # Output is in /app/frontend/dist # ============================================================ # STAGE 2: Build the Spring Boot Backend # ============================================================ FROM maven:3.9.6-eclipse-temurin-21 AS backend-build WORKDIR /app/backend # Cache Maven dependencies separately from source COPY quran-app-backend/pom.xml . RUN mvn dependency:go-offline -B # Build the JAR COPY quran-app-backend/src ./src RUN mvn clean package -DskipTests # Output is in /app/backend/target/*.jar # ============================================================ # STAGE 3: Final runtime image # ============================================================ FROM eclipse-temurin:21-jre-jammy WORKDIR /app # ---------- system packages ---------- RUN apt-get update && apt-get install -y --no-install-recommends \ nginx \ ffmpeg \ && rm -rf /var/lib/apt/lists/* # ---------- Nginx config ---------- # Nginx listens on 7860 (the port HuggingFace Spaces exposes). # Static files are served directly; /api/** and /ws/** are proxied # to the Spring Boot process running on 3000 internally. RUN rm -f /etc/nginx/sites-enabled/default COPY <<'NGINX_EOF' /etc/nginx/conf.d/quranapp.conf server { listen 7860; server_name _; # Serve the React SPA root /app/static; index index.html; # API proxy → Spring Boot location /api/ { proxy_pass http://127.0.0.1:3000/api/; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 300s; proxy_send_timeout 300s; proxy_connect_timeout 300s; client_max_body_size 50M; } # WebSocket proxy → Spring Boot location /ws/ { proxy_pass http://127.0.0.1:3000/ws/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_read_timeout 3600s; } # SPA fallback — all other paths return index.html location / { try_files $uri $uri/ /index.html; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } } NGINX_EOF # ---------- Copy built artifacts ---------- COPY --from=frontend-build /app/frontend/dist /app/static COPY --from=backend-build /app/backend/target/*.jar /app/app.jar # ---------- Startup script ---------- # HuggingFace Spaces require a single process entry point. # We start Spring Boot in the background, then hand control to Nginx. COPY <<'STARTUP_EOF' /app/start.sh #!/bin/bash set -e echo "==> Starting Spring Boot on port 3000..." java -jar /app/app.jar & echo "==> Waiting for Spring Boot to be ready..." until curl -sf http://127.0.0.1:3000/actuator/health > /dev/null 2>&1; do sleep 2 done echo "==> Spring Boot is ready. Starting Nginx on port 7860..." nginx -g "daemon off;" STARTUP_EOF RUN chmod +x /app/start.sh # HuggingFace Spaces only expose port 7860 EXPOSE 7860 CMD ["/app/start.sh"]