Spaces:
Sleeping
Deploying to Hugging Face Spaces (free tier, Docker SDK)
This document is the canonical guide for the free-of-cost deployment
target the project is configured for. The image is built from the repo's
existing Dockerfile, runs as UID 1000 (Spaces requirement), and listens on
port 7860 (Spaces convention). All other configuration is via environment
variables / Space secrets β the source code is identical across deployments.
Cost reality check The Space itself is free. OpenAI API calls are not. Every report generation, embedding, vision call, and notes upload bills against the
OPENAI_API_KEYyou provide. Typical cost per generated report ongpt-4o-mini+text-embedding-3-smallis roughly Β£0.04 β Β£0.25.
1. Prerequisites
| Item | Where |
|---|---|
| Hugging Face account | https://huggingface.co/join |
| Personal OpenAI API key | https://platform.openai.com/api-keys |
Locally installed git |
already present in this repo |
You don't need a paid HF subscription. The free CPU Space tier is enough.
Spaces in this project:
| Space | Git remote | URL |
|---|---|---|
StormShadow308/RICS |
hf or space |
https://huggingface.co/spaces/StormShadow308/RICS |
StormShadow308/TestBunny |
testbunny |
https://huggingface.co/spaces/StormShadow308/TestBunny |
If HF created TestBunny with the default app.py + tiny Dockerfile template, replace it entirely by pushing this repo (see Β§2b). Do not merge the template with Report Genius β use our root Dockerfile and app.main:app.
2. Create the Space (one time)
- Go to https://huggingface.co/new-space.
- Fill in:
- Owner / Space name β anything, e.g.
your-handle/rics-report-genius. - License β match the repo (
mit). - SDK β choose Docker β Blank.
- Space hardware β
CPU basic(free). - Visibility β Public is fine; Private also works (no behaviour change).
- Owner / Space name β anything, e.g.
- Click Create Space. HF will create an empty Git repo at
https://huggingface.co/spaces/<owner>/<space-name>.
2b. Deploy Report Genius to an existing Space (e.g. TestBunny)
From your machine (HF token as password when prompted):
cd "D:\RICS UK Project"
git remote add testbunny https://huggingface.co/spaces/StormShadow308/TestBunny
git push testbunny experiment-lab:main
Or with the HF CLI:
hf auth login
git push testbunny HEAD:main
The Space must use Docker SDK (not Gradio). Our README.md already has the required YAML (sdk: docker, app_port: 7860). The image listens on 7860 and runs uvicorn app.main:app β not the template app.py world handler.
3. Add the YAML frontmatter to README.md
HF Spaces reads metadata from the first YAML block of README.md in the
Space repo. Open the project's README.md and prepend exactly this block at
the very top (above the existing # Report Genius AI heading):
---
title: RICS Report Genius
emoji: π
colorFrom: blue
colorTo: indigo
sdk: docker
app_port: 7860
pinned: false
license: mit
short_description: RICS-style RAG report generator (FAISS + GPT-4o-mini).
---
That YAML must be the first characters of the file β no blank line, no
BOM, nothing before the opening ---.
Do this in a separate commit on a
huggingfacebranch (or directly on the branch you push to the Space) so the GitHub README isn't littered with HF metadata permanently. Locally the file renders fine on both platforms.
3b. Pre-flight before a demo
After the Space is running:
python scripts/preflight_hf.py --base https://<your-space-subdomain>.hf.space --tenant your-tenant-id
All checks must pass (especially parallel_multi_section and indexed_chunk_count after upload).
4. Set the Space secrets
In the Space's Settings β Variables and secrets, add at minimum:
| Type | Name | Value |
|---|---|---|
| Secret | OPENAI_API_KEY |
your OpenAI key (sk-β¦) β required |
| Secret | TENANT_SECRET_KEY |
any 32+ char random string (recommended) |
Production AI profile (recommended on Spaces): the app auto-detects SPACE_ID and
disables ingest-time LLM sanitisation so uploads do not burn your OpenAI quota before
report generation. Set these Variables so inspector + photo vision run on the main
/generate path:
| Type | Name | Value |
|---|---|---|
| Variable | INSPECTOR_TOOL_AGENT |
true |
| Variable | AGENTIC_INSPECTOR_WHEN_NOTES_ONLY |
true |
| Variable | SECTION_PHOTO_VISION_ENABLED |
true |
| Variable | SECTION_PHOTO_VISION_MODEL |
gpt-4o (optional; better vision than mini) |
| Variable | PRIMARY_GENERATE_PIPELINE |
agentic |
| Variable | ENABLE_RAG_UPLOAD_SANITISATION |
true (regex redaction; LLM sanitisation stays off on Space) |
| Variable | RAG_SANITISATION_USE_LLM |
false |
| Secret | HF_TOKEN |
optional β faster all-MiniLM-L6-v2 download on cold start |
Required vs recommended. The container ships with
DEV_MODE=false, which engages a startup guard inapp/main.py. IfOPENAI_API_KEYis empty/whitespace, the container will refuse to start with a clearRuntimeErrorin the Logs tab β that is intentional. The agentic inspector would otherwise silently fall back to crude keyword heuristics and reports would be low quality without you knowing.
TENANT_SECRET_KEYis currently inert (no signing path consumes it) but the guard logs a loudWARNINGif it equals one of the sentinel placeholders ("","dev-secret-change-me","change-me-in-production"). Set it now so the day someone wires it into tenant-token signing, you don't have to redeploy.
Optional β override Dockerfile defaults if you want different behaviour:
| Type | Name | Default | Notes |
|---|---|---|---|
| Variable | KNOWLEDGE_BASE_ENABLED |
false |
Set true only if you also bake KB PDFs into the image. The startup guard logs a WARNING when this is false in production β that's expected on the HF free tier (KB source PDFs are gitignored), so the warning is informational. |
| Variable | INSPECTOR_TOOL_AGENT |
true |
Disable to force the legacy fixed pipeline. |
| Variable | AGENTIC_INSPECTOR_WHEN_NOTES_ONLY |
true |
Inspector + vision on POST /generate. Slower than standard-only batch; use false if you need sub-10m full reports without vision. |
| Variable | GENERATION_SLA_SECONDS |
600 |
Product target (10 min). SPACE_ID auto-enables async parallel sections. |
| Variable | SECTION_PHOTO_VISION_ENABLED |
true |
Vision analysis of section photos. |
| Variable | SECTION_PHOTO_ANALYZE_ON_UPLOAD |
true |
Run vision after each photo upload (cached per section). |
| Variable | PERSONALISED_STYLE_RAG_ENABLED |
true |
Private per-tenant style library (default product mode). |
| Variable | ENABLE_RAG_UPLOAD_SANITISATION |
true |
Regex redaction before indexing (forced on when personalised RAG is on). Do not set RAG_SANITISATION_USE_LLM=true on HF. |
| Variable | RAG_SANITISATION_USE_LLM |
false |
Auto-disabled when SPACE_ID is set. |
| Variable | UVICORN_WORKERS |
1 |
Correct for the 1-vCPU HF Spaces free tier. On bigger hosts, set to (2 * CPU cores) + 1. |
| Variable | RATE_LIMIT_GENERATE_RPM |
20 |
Per-tenant /generate rate limit. |
| Variable | RATE_LIMIT_READ_RPM |
120 |
Per-tenant read rate limit. |
You should never put OPENAI_API_KEY in a Variable β that exposes it
in the public Space metadata. Always use Secret.
5. Push the code
The Space is just a git remote. From the project root:
# one-time: add the Space as a remote (use https or ssh)
git remote add space https://huggingface.co/spaces/<owner>/<space-name>
# push (force-with-lease the first time if the Space already has an empty README)
git push space HEAD:main
If you forked the repo on GitHub and want a 1-click sync, you can also configure the Space to Sync from GitHub in Settings β Repository instead of pushing manually.
Pre-push image size sanity check (optional)
HF Spaces enforces a hard image-size cap (~10 GB on free Docker Spaces). With the current Dockerfile the image lands around 1.2β1.5 GB. To verify locally before pushing (requires Docker with β₯ 5 GB free disk):
docker build --target production -t rics-genius:check .
docker images rics-genius:check --format '{{.Size}}'
# expected: ~1.2-1.5 GB. If >5 GB, rebuild with `--no-cache` and check that
# .dockerignore is being honoured (Behrang RICS Documents/, *.db, etc.).
6. First boot β what to expect
HF builds the Docker image (5β8 min the first time). When it's ready you'll see logs in the Logs tab roughly like:
INFO: Started server process [1]
WARNING app.main β KNOWLEDGE_BASE_ENABLED=false in production: ... (expected on HF free tier)
INFO app.main β Initialising databaseβ¦
INFO app.main β Pre-warming vector storeβ¦
INFO app.vectorstore.faiss_wrapper β Created new LangChain FAISS index at /home/user/.report_genius/faiss_index
INFO app.main β Pre-warming embedding modelβ¦
INFO app.main β RICS inspector runtime: mode=openai_tool_agent key=True flag=True
INFO app.main β Startup complete.
INFO: Uvicorn running on http://0.0.0.0:7860
The KNOWLEDGE_BASE_ENABLED=false warning is expected on the free tier β
the source PDFs are gitignored, so there is nothing to seed. Tenants supply
their own corpus via uploads.
If instead you see:
RuntimeError: OPENAI_API_KEY is required when DEV_MODE=false. ...
β¦the startup guard fired because OPENAI_API_KEY is missing or whitespace β
re-save it in Settings β Secrets, the Space restarts automatically.
The Space's public URL (https://<owner>-<space-name>.hf.space) now serves
the FastAPI app. /health returns 200, / returns the frontend. The
container also has a Docker-level HEALTHCHECK that polls /health every
30 s, so the Space dashboard reflects readiness independently of port-binding.
7. Persistence β the trade-off you implicitly made
Free CPU Spaces have an ephemeral filesystem. Every restart (idle, crash, image rebuild, manual restart) wipes:
dev.dbβ tenants, reports, document rows, section text.~/.report_genius/faiss_indexβ the embedding index.~/.report_genius/uploadsβ uploaded PDFs/DOCX.
What survives across restarts: only the source code in the image.
That matches the hybrid option you picked: tenant uploads are expected to be ephemeral on the free tier. If you later want true persistence, you have three options, in order of effort:
Pay for HF Persistent Storage ($5/mo for 20 GB on
/data). AddVariable: HF_HOME=/dataand changeDATABASE_URL,FAISS_INDEX_PATH,UPLOAD_DIRto point under/data. Smallest code change, fastest deploy.Sync FAISS + DB to a private HF Dataset on shutdown. Requires writing a small
app/services/hf_index_sync.pyand hooking it into FastAPI'slifespanshutdown. ~50 lines +huggingface_hubdep. Free, but you lose anything written between snapshots.Move state out of the container β use a hosted Postgres (Supabase free tier, Neon) for
DATABASE_URLand an external object store for uploads. Most robust, biggest plumbing change.
If you want any of these wired up, ask and I'll do it as a follow-up.
8. Troubleshooting
"Space exited with error" during build
Check the Logs tab. The most common causes are:
- Build context too big β make sure
.dockerignoreis committed.Behrang RICS Documents/,RAW Context/,*.db,.claude/,app/tests/and similar must NOT enter the build context. - Image too large β see Β§5 "Pre-push image size sanity check". The
current Dockerfile installs the project non-editable, without
[dev]extras, so a clean image is ~1.2β1.5 GB. If yours is much bigger, something is leaking into the context.
"RuntimeError: OPENAI_API_KEY is required when DEV_MODE=false"
This is the production startup guard doing its job. Re-add the
OPENAI_API_KEY secret in Settings β Secrets (whitespace doesn't count
β it must be a real sk-β¦ key). Save; the Space restarts automatically.
App boots but / 502s
HF's reverse proxy expects port 7860. Confirm:
app_port: 7860in the README YAML.- Container CMD listens on
7860(the default in the Dockerfile whenPORTis unset).
"Internal Server Error" on first generation
With the new startup guard the container won't even boot if
OPENAI_API_KEY is missing β so a 500 here is more likely an OpenAI rate
limit, expired key, or model availability issue. Check /health under
rics_inspector.openai_api_key_configured (should be true) and watch the
Logs tab for the actual upstream OpenAI error.
KB-related logs you can ignore
WARNING app.main β KNOWLEDGE_BASE_ENABLED=false in production: ...
This is the production guard reminding you that the agentic inspector's KB-grounding tool returns nothing in this deployment. Expected on the HF free tier; see Β§6.
Container restart drops in-flight requests
Shouldn't happen with the current Dockerfile β CMD uses an exec-form
shell wrapper so uvicorn becomes PID 1 and receives SIGTERM directly. If
you see clients getting connection-resets on every redeploy, verify the
running image's CMD with docker inspect <image> --format '{{.Config.Cmd}}':
it must contain exec uvicorn.
Out of memory
The free CPU tier has 16 GB RAM but a slow CPU. The app idles around ~600 MB plus the FAISS index. If you hit OOM after heavy usage, restart the Space (it clears the index). The deeper fix is option (1) or (3) in Β§7.
9. Updating the Space
git push space HEAD:main
HF auto-rebuilds and reboots. Build time is faster after the first deploy because the base image and venv are cached.
To force a clean rebuild, use Settings β Factory rebuild.
10. Reverting the deployment
In Settings β Delete this Space. The container, image cache, and ephemeral data are removed. Your repo is untouched.