# PawTrace — single-service image for the READ-ONLY demo (Hugging Face Spaces / any Docker host). # Builds the React frontend, then runs the FastAPI API which also serves that frontend (one origin, # no CORS). Bundles the fine-tuned PyTorch re-ID model (best.pt) + the HF breed classifier + the # 1,000-dog demo database, and runs with DEMO_MODE=true so every write is blocked server-side. # Needs ~1 GB RAM with both models loaded — fine on a Spaces CPU-basic (16 GB) box. # ---- Stage 1: build the React frontend -> /web/dist ---- FROM node:20-slim AS frontend WORKDIR /web COPY frontend/package.json frontend/package-lock.json ./ RUN npm ci COPY frontend/ ./ RUN npm run build # ---- Stage 2: Python API (serves the built frontend) ---- FROM python:3.12-slim AS app ENV PYTHONUNBUFFERED=1 \ PIP_NO_CACHE_DIR=1 WORKDIR /app # Base runtime deps (FastAPI, SQLAlchemy, Pillow, numpy, ...). COPY backend/requirements-base.txt ./requirements-base.txt RUN pip install --upgrade pip && pip install -r requirements-base.txt # ---- REAL MATCHING (EMBEDDER=reid + BREED_CLASSIFIER=hf) ---------------------------------------- # PyTorch (CPU build) + transformers power the real re-ID embedder and breed classifier. RUN pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu \ && pip install "transformers>=4.40" "safetensors>=0.4" # Pre-download the model into the image (into HF_HOME) so the first live match doesn't stall on a # runtime download. This is a big, stable layer — kept cached across code changes below. ENV HF_HOME=/app/hf_cache RUN python -c "from transformers import AutoImageProcessor, AutoModel, AutoModelForImageClassification as M; \ k='jhoppanne/Dogs-Breed-Image-Classification-V1'; AutoImageProcessor.from_pretrained(k); \ M.from_pretrained(k); AutoModel.from_pretrained(k)" # ------------------------------------------------------------------------------------------------ # Backend source (includes backend/demo_data/ — the shipped snapshot). COPY backend/ ./ # Fine-tuned re-ID model weights (git-LFS in the Space repo) -> loaded when EMBEDDER=reid. COPY best.pt /app/best.pt # Geo centroid CSV lives at the repo root; copy it in and point the app at it. COPY data/zip_centroids.csv /app/geo/zip_centroids.csv ENV ZIP_CENTROID_FILE=/app/geo/zip_centroids.csv # Built frontend from stage 1 (the API serves this at "/"). COPY --from=frontend /web/dist ./frontend_dist # Bake the 1,000-dog demo snapshot (SQLite DB + processed photos) into the image's data dir, then # drop the source copy. DEMO_MODE blocks all writes, so the DB never changes; a redeploy just # reloads this same read-only snapshot. RUN mkdir -p /app/data \ && cp /app/demo_data/app.db /app/data/app.db \ && cp -r /app/demo_data/media /app/data/media \ && rm -rf /app/demo_data ENV DATABASE_URL=sqlite:////app/data/app.db \ MEDIA_DIR=/app/data/media \ EMBEDDER=reid \ REID_MODEL_PATH=/app/best.pt \ REID_MODEL_VERSION=v4 \ BREED_CLASSIFIER=hf \ BREED_TOP_K=10 \ DEMO_MODE=true \ HF_HUB_OFFLINE=1 \ TRANSFORMERS_OFFLINE=1 # HF_HUB_OFFLINE/TRANSFORMERS_OFFLINE: use the model baked into HF_HOME above; never call # huggingface.co at runtime (faster cold start, no external dependency during a demo). # HF Spaces routes to the port declared as `app_port` in README.md (7860). Bind there; ${PORT} keeps # it portable to hosts that inject a port (Render, etc.). EXPOSE 7860 CMD ["sh", "-c", "uvicorn app.main:app --host 0.0.0.0 --port ${PORT:-7860}"]