| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Stage 1: Build the React / Vite frontend | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| FROM node:20-alpine AS frontend-builder | |
| WORKDIR /app/frontend | |
| # Cache npm install layer separately from source | |
| COPY package*.json ./ | |
| RUN npm ci --prefer-offline && npm cache clean --force | |
| # Copy source and produce the production bundle | |
| COPY . . | |
| RUN npm run build | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Stage 2: Build the Go backend (statically linked binary) | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| FROM golang:1.25-alpine AS backend-builder | |
| WORKDIR /app | |
| # git is required by some go module downloads | |
| RUN apk add --no-cache git | |
| # Download dependencies first for better layer caching | |
| COPY go.mod go.sum ./ | |
| RUN go mod download && go mod verify | |
| # Copy all source and compile a fully-static binary | |
| COPY . . | |
| RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \ | |
| -ldflags='-w -s -extldflags "-static"' \ | |
| -a -installsuffix cgo \ | |
| -o server \ | |
| ./cmd/server | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Stage 3: Minimal runtime image | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| FROM alpine:3.19 | |
| # Runtime deps: | |
| # ca-certificates β HTTPS requests to MongoDB Atlas / Groq | |
| # tzdata β timezone support for streak logic | |
| # wget β used by the HEALTHCHECK below | |
| RUN apk --no-cache add \ | |
| ca-certificates \ | |
| tzdata \ | |
| wget \ | |
| && update-ca-certificates | |
| # Non-root user (required by HuggingFace Spaces and general best practice) | |
| RUN addgroup -g 1001 -S appgroup && \ | |
| adduser -u 1001 -S appuser -G appgroup | |
| WORKDIR /app | |
| # Copy the compiled Go binary | |
| COPY --from=backend-builder /app/server . | |
| RUN chmod +x server | |
| # Copy the built React app β Go serves it as static files from ./dist | |
| COPY --from=frontend-builder /app/frontend/dist ./dist | |
| # Fix ownership before switching user | |
| RUN chown -R appuser:appgroup /app | |
| USER appuser | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Runtime configuration | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # HuggingFace Spaces requires port 7860. | |
| # The Go server reads $PORT at startup and falls back to 7860. | |
| ENV PORT=7860 | |
| # Required secrets β set these in HuggingFace Space Settings β Variables and secrets: | |
| # MONGODB_URI MongoDB Atlas connection string | |
| # JWT_SECRET Long random string for signing sessions | |
| # GROQ_API_KEY Groq API key for AI features (optional, degrades gracefully) | |
| EXPOSE 7860 | |
| HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \ | |
| CMD wget --no-verbose --tries=1 --spider http://localhost:7860/health || exit 1 | |
| CMD ["./server"] | |