deploy: HF Spaces β combined single-container imageWraps our existing 2-service compose into one image that HF Spaces' Docker SDK can run on its standard 7860 port.- Dockerfile (root): three-stage build1. node:20-alpine β npm ci + npm run build (produces ui/dist/)2. python:3.11-slim β pip install into an isolated /opt/venv3. python:3.11-slim + nginx (via apt) + tini as PID 1- docker/nginx.hf.conf: variant listening on 7860, proxies /api β 127.0.0.1:8000- docker/hf-entrypoint.sh: launches uvicorn in the background bound to127.0.0.1:8000 (nginx-only reachable), polls /health for up to 30s songinx isn't serving 502s during boot, then execs nginx in the foreground.Trap forwards SIGTERM to uvicorn for clean shutdown.- README.md: YAML frontmatter block (title, sdk: docker, app_port: 7860)that HF Spaces reads to configure the Space. Also added the π€ HF badgeand a 'Deploy your own' section for anyone forking.CI is unaffected β the existing docker/api.Dockerfile + docker/ui.Dockerfilestill drive the multi-container compose + CI docker-build job. This rootDockerfile is HF-only and doesn't touch the compose stack.
f3aa131 | # ============================================================================ | |
| # HF Space entrypoint. Runs uvicorn in the background and nginx in the | |
| # foreground so PID 1 is nginx (and stops cleanly on SIGTERM from HF's | |
| # infrastructure). | |
| # ============================================================================ | |
| set -eu | |
| # Sanity check: HF's secret injection puts OPENAI_API_KEY on env. | |
| # Log a friendly warning if it's missing β don't hard-fail so the UI still | |
| # loads and can show the /schemas endpoint. | |
| if [ -z "${OPENAI_API_KEY:-}" ]; then | |
| echo "[hf-entrypoint] WARN: OPENAI_API_KEY is not set. /extract will 500." | |
| echo "[hf-entrypoint] Add it under Space Settings β Repository secrets." | |
| fi | |
| # --- background: uvicorn ----------------------------------------------------- | |
| # Bind to localhost only β nginx is the only thing that talks to it. | |
| # 1 worker keeps the DocumentExtractor singleton coherent (matches api.Dockerfile). | |
| uvicorn src.api.main:app \ | |
| --host 127.0.0.1 \ | |
| --port 8000 \ | |
| --workers 1 \ | |
| --log-level info & | |
| UVICORN_PID=$! | |
| echo "[hf-entrypoint] uvicorn started (pid $UVICORN_PID)" | |
| # Trap so a SIGTERM to nginx also stops uvicorn. | |
| term() { | |
| echo "[hf-entrypoint] shutting down" | |
| kill -TERM "$UVICORN_PID" 2>/dev/null || true | |
| wait "$UVICORN_PID" 2>/dev/null || true | |
| exit 0 | |
| } | |
| trap term TERM INT | |
| # --- wait until uvicorn is ready before starting nginx ---------------------- | |
| # HF's infra checks the app port; we don't want nginx serving 502s while | |
| # uvicorn is still importing pdfplumber/pymupdf (which is slow). | |
| for i in $(seq 1 30); do | |
| if curl -fsS http://127.0.0.1:8000/health >/dev/null 2>&1; then | |
| echo "[hf-entrypoint] api healthy after ${i}s" | |
| break | |
| fi | |
| sleep 1 | |
| done | |
| # --- foreground: nginx ------------------------------------------------------- | |
| echo "[hf-entrypoint] starting nginx on :7860" | |
| exec nginx -g 'daemon off;' | |