# ───────────────────────────────────────────── # Stage 1 — Build the React frontend # ───────────────────────────────────────────── FROM node:20-alpine AS frontend-builder WORKDIR /app/frontend # Install dependencies first (cache-friendly) COPY frontend/package*.json ./ RUN npm ci # Copy source and build COPY frontend/ ./ RUN npm run build # ───────────────────────────────────────────── # Stage 2 — Build the Go backend # ───────────────────────────────────────────── FROM golang:1.21-alpine AS backend-builder WORKDIR /app/backend # Copy go module files first for caching COPY backend/go.mod backend/go.sum ./ RUN go mod download # Copy source and build a static binary COPY backend/ ./ RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /portfolio-api ./cmd/main.go # ───────────────────────────────────────────── # Stage 3 — Final image with Nginx + Go binary # ───────────────────────────────────────────── FROM nginx:1.25-alpine # Install supervisord to run two processes RUN apk add --no-cache supervisor # Copy built frontend into Nginx web root COPY --from=frontend-builder /app/frontend/dist /usr/share/nginx/html # Copy compiled Go binary COPY --from=backend-builder /portfolio-api /app/portfolio-api # Copy Nginx config COPY nginx.conf /etc/nginx/nginx.conf # Copy supervisord config COPY supervisord.conf /etc/supervisord.conf # Hugging Face Spaces injects a random non-root UID at runtime. # We can't use USER 1000 because that UID may not exist in /etc/passwd. # Instead we make all writable paths accessible to any UID. RUN mkdir -p /var/cache/nginx /var/log/nginx /var/run /tmp/nginx \ && chmod -R 777 /var/cache/nginx /var/log/nginx /var/run /tmp/nginx \ && chmod -R 755 /usr/share/nginx/html \ && chmod +x /app/portfolio-api \ # Allow nginx to write its PID to /tmp (world-writable) instead of /var/run && sed -i 's|/var/run/nginx.pid|/tmp/nginx/nginx.pid|g' /etc/nginx/nginx.conf 2>/dev/null || true EXPOSE 7860 CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"]