# GrantForge AI — Multi-stage Docker build # Backend: FastAPI + LangGraph | Frontend: Vite React # ────────────────────────────────────────────────────────────────── # STAGE 1: Frontend build # ────────────────────────────────────────────────────────────────── FROM node:20-slim AS frontend-builder WORKDIR /app/frontend COPY frontend-react/package*.json ./ RUN rm -f package-lock.json && npm install COPY frontend-react/ ./ RUN npm run build # Artefakt: /app/frontend/dist/ # ────────────────────────────────────────────────────────────────── # STAGE 2: Python dependencies # ────────────────────────────────────────────────────────────────── FROM python:3.11.9-slim AS python-deps WORKDIR /install RUN apt-get update && apt-get install -y --no-install-recommends \ libpq-dev gcc g++ libffi-dev libglib2.0-0 libpango-1.0-0 \ libpangocairo-1.0-0 libcairo2 libcairo2-dev pkg-config python3-dev \ && rm -rf /var/lib/apt/lists/* COPY backend/requirements.txt . RUN pip install --no-cache-dir --prefix=/install/pkg -r requirements.txt && \ rm -rf /install/pkg/lib/python3.11/site-packages/pinecone_plugin_inference* # ────────────────────────────────────────────────────────────────── # STAGE 3: Runtime image # ────────────────────────────────────────────────────────────────── FROM python:3.11.9-slim AS runtime WORKDIR /app COPY --from=python-deps /install/pkg /usr/local RUN apt-get update && apt-get install -y --no-install-recommends \ libpq5 libglib2.0-0 libpango-1.0-0 libpangocairo-1.0-0 libcairo2 wget \ && rm -rf /var/lib/apt/lists/* COPY backend/ ./backend/ COPY --from=frontend-builder /app/frontend/dist ./static/ RUN mkdir -p /app/backend/assets && \ wget -qO /app/backend/assets/Roboto-Regular.ttf "https://github.com/googlefonts/roboto/raw/main/src/hinted/Roboto-Regular.ttf" && \ wget -qO /app/backend/assets/Roboto-Bold.ttf "https://github.com/googlefonts/roboto/raw/main/src/hinted/Roboto-Bold.ttf" RUN mkdir -p /app/backend/cache RUN useradd -m -u 1001 appuser && chown -R appuser:appuser /app USER appuser WORKDIR /app/backend EXPOSE 7860 HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \ CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:7860/api/health')" CMD ["gunicorn", "server:app", \ "--worker-class", "uvicorn.workers.UvicornWorker", \ "--workers", "2", \ "--bind", "0.0.0.0:7860", \ "--timeout", "120", \ "--access-logfile", "-"]