Spaces:
Running
Running
| # syntax=docker/dockerfile:1 | |
| # | |
| # Gridlock Traffic Intelligence — single-image demo. | |
| # One container serves the React frontend AND the FastAPI inference API on one | |
| # port. Runs unchanged on Hugging Face Spaces (Docker SDK), Render, or any host. | |
| # | |
| # This Dockerfile lives at the repo root because Hugging Face Spaces builds the | |
| # `Dockerfile` at the root with the repo as context. Build context = repo root: | |
| # docker build -t gridlock . | |
| # docker run -p 8000:8000 gridlock # -> http://localhost:8000 | |
| # | |
| # ---------------------------------------------------------------------------- # | |
| # Stage 1 — build the frontend with Node, output static assets to /build/dist | |
| # ---------------------------------------------------------------------------- # | |
| FROM node:20-slim AS frontend | |
| WORKDIR /build | |
| # Install deps first (cached unless the lockfile changes). | |
| COPY app/frontend/package.json app/frontend/package-lock.json ./ | |
| RUN npm ci | |
| # Build the production bundle. | |
| COPY app/frontend/ ./ | |
| RUN npm run build | |
| # ---------------------------------------------------------------------------- # | |
| # Stage 2 — Python runtime serving API + the built frontend | |
| # ---------------------------------------------------------------------------- # | |
| FROM python:3.10-slim AS runtime | |
| ENV PYTHONUNBUFFERED=1 \ | |
| PYTHONDONTWRITEBYTECODE=1 \ | |
| PIP_NO_CACHE_DIR=1 | |
| WORKDIR /app | |
| # libgomp1 is required by LightGBM / XGBoost at runtime. | |
| RUN apt-get update \ | |
| && apt-get install -y --no-install-recommends libgomp1 \ | |
| && rm -rf /var/lib/apt/lists/* | |
| # Install the CPU-only PyTorch wheel first (avoids pulling ~2GB of CUDA libs), | |
| # then the remaining inference dependencies. torch==2.12.1 in requirements is | |
| # then already satisfied by the +cpu build, so it is not re-resolved from PyPI. | |
| # pip is upgraded first because the 23.0.1 shipped in slim rejects the PyTorch | |
| # index's `typing_extensions` wheels (name-normalization bug). xgboost declares | |
| # nvidia-nccl-cu12 (~300MB) for multi-GPU only; CPU inference never loads it, so | |
| # we drop it (scoped `|| true` keeps the build green if it is already absent). | |
| COPY app/requirements.txt ./requirements.txt | |
| RUN pip install --no-cache-dir --upgrade pip \ | |
| && pip install --no-cache-dir torch==2.12.1 --index-url https://download.pytorch.org/whl/cpu \ | |
| && pip install --no-cache-dir -r requirements.txt \ | |
| && { pip uninstall -y nvidia-nccl-cu12 || true; } | |
| # Hugging Face Spaces runs the container as UID 1000. Create that user and switch | |
| # to it BEFORE downloading the model + copying code, so the HF cache and app | |
| # files are owned by the runtime user without a costly `chown -R` layer. This is | |
| # also valid on Render / any host (uvicorn binds a high port, so no root needed). | |
| RUN useradd -m -u 1000 user | |
| USER user | |
| ENV HOME=/home/user \ | |
| PATH=/home/user/.local/bin:$PATH \ | |
| HF_HOME=/home/user/.cache/huggingface | |
| # Pre-download the multilingual sentence-transformer into the image (owned by | |
| # `user`) so the first /api/predict is fast and the container needs no network. | |
| RUN python -c "from sentence_transformers import SentenceTransformer; \ | |
| SentenceTransformer('sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2')" | |
| # ---- Application code + the artifacts inference actually loads (owned by user) ---- # | |
| COPY --chown=user:user src/ ./src/ | |
| COPY --chown=user:user hotspot_model.py ./hotspot_model.py | |
| COPY --chown=user:user app/__init__.py ./app/__init__.py | |
| COPY --chown=user:user app/backend/ ./app/backend/ | |
| # Trained models + preprocessors (T1–T4) and the geo KMeans. | |
| COPY --chown=user:user models/ ./models/ | |
| # Hotspot bundle + replay history (the raw training CSV is NOT shipped). | |
| COPY --chown=user:user hotspot_artifacts/hotspot_bundle.joblib hotspot_artifacts/hotspot_history.parquet hotspot_artifacts/hotspot_metrics.json ./hotspot_artifacts/ | |
| # Causal target-rate history used by the feature pipeline at inference time. | |
| COPY --chown=user:user data/processed/history.parquet ./data/processed/history.parquet | |
| # Report figures (served by /api/figures) + metric JSON. | |
| COPY --chown=user:user reports/ ./reports/ | |
| # Built frontend from stage 1. | |
| COPY --chown=user:user --from=frontend /build/dist ./app/frontend/dist | |
| # Build-time smoke test: load every pickled artifact (T1-T4 models, preprocessors, | |
| # hotspot bundle + histories) through the real inference service. This fails the | |
| # build early if there is any library-version skew or a missing file, rather than | |
| # surfacing it at runtime on the first request. | |
| RUN python -c "from app.backend.inference import get_service; get_service(); print('Inference artifacts load OK')" | |
| # Skip network calls to Hugging Face now that the model is cached in the image. | |
| ENV HF_HUB_OFFLINE=1 \ | |
| TRANSFORMERS_OFFLINE=1 \ | |
| PORT=8000 | |
| # HF Spaces routes to the port declared as `app_port` in README.md (8000 here); | |
| # Render injects $PORT. Both are honoured by the CMD below. | |
| EXPOSE 8000 | |
| CMD ["sh", "-c", "uvicorn app.backend.main:app --host 0.0.0.0 --port ${PORT:-8000}"] | |