Render
The primary deployment path for this project. Free web services, no credit card, deploys straight from a GitHub repo.
Two ways to do it
Section titled “Two ways to do it”The repo already contains deploy/render.yaml, so the settings are in
version control rather than in someone’s memory.
- Render → New + → Blueprint
- Pick your repo
- Render reads the file and asks you for the three secret values
- Apply
Render → New + → Web Service → connect your repo, then:
| Field | Value |
|---|---|
| Root Directory | app |
| Runtime | Python |
| Build Command | pip install uv && uv sync --frozen |
| Start Command | uv run fastapi run main.py --port $PORT |
| Instance Type | Free |
| Health Check Path | /health |
The blueprint, explained
Section titled “The blueprint, explained”services: - type: web name: research-agent runtime: python plan: free rootDir: app buildCommand: pip install uv && uv sync --frozen startCommand: uv run fastapi run main.py --port $PORT healthCheckPath: /health
envVars: - key: LLM_API_KEY sync: false - key: SUPABASE_URL sync: false - key: SUPABASE_SECRET_KEY sync: false - key: TAVILY_API_KEY sync: false
- key: LLM_MODEL value: gemini-flash-latest - key: LLM_PROVIDER value: google - key: MAX_AGENT_STEPS value: "12" - key: MAX_THREAD_TURNS value: "10" - key: ALLOWED_ORIGINS value: "*"Four things in there are worth knowing:
rootDir: apppoints Render at the uv project —pyproject.toml,uv.lock, and the app code all live inapp/, not the repo root. Every other path in the blueprint, includingbuildCommandandstartCommand, resolves relative to it.uv sync --frozenmeans “useuv.lockexactly, don’t re-resolve”. Your deploy gets the versions you tested, not whatever is newest today.sync: falsemeans “ask me for this value in the dashboard, and never store it in the repo”. This is how secrets stay out of git — the four keys above it are secret, so none of them carry a value here.TAVILY_API_KEYis the one that’s also optional: leave it empty in the dashboard and the agent runs on Wikipedia alone.- The variables that do carry a value (
LLM_MODEL,LLM_PROVIDER,MAX_AGENT_STEPS,MAX_THREAD_TURNS,ALLOWED_ORIGINS) aren’t secret, so committing them is fine. ChangeALLOWED_ORIGINSto your real frontend origin before you go public.
Environment variables
Section titled “Environment variables”Dashboard → your service → Environment.
| Variable | Required | Notes |
|---|---|---|
LLM_API_KEY |
yes | from AI Studio, or whichever provider you chose |
SUPABASE_URL |
yes | no trailing slash |
SUPABASE_SECRET_KEY |
yes | the sb_secret_… key, not the publishable one. On a pre-rename project, the legacy service_role key works here too |
LLM_MODEL |
on Google only | defaults to gemini-flash-latest; required for every other provider |
LLM_PROVIDER |
no | google (default), cerebras, openrouter, openai-compatible |
LLM_BASE_URL |
only for openai-compatible |
e.g. https://api.groq.com/openai/v1 |
TAVILY_API_KEY |
no | enables web search, so the agent can answer questions about recent events |
MAX_AGENT_STEPS |
no | defaults to 12 |
MAX_THREAD_TURNS |
no | defaults to 10 — the cap on one conversation |
ALLOWED_ORIGINS |
no | comma-separated, no trailing slash |
The old GEMINI_API_KEY and GEMINI_MODEL names still work when
LLM_PROVIDER is google, so a service deployed before this change keeps
running untouched.
What the free tier actually gives you
Section titled “What the free tier actually gives you”| Instance hours | 750 / month / workspace |
| Credit card | not required |
| Spin-down | after ~15 minutes idle |
| Cold start | ~1 minute |
| CPU | a small shared slice |
| Filesystem | ephemeral — wiped on every restart |
Two of these bite people:
The cold start. Hit /health ten minutes before any demo. A judge or
interviewer waiting 60 seconds on a blank page will assume it’s broken.
The ephemeral filesystem. Anything written to disk is gone on restart. This is why the project keeps everything in Postgres and nothing on disk — it’s not an accident.
The small CPU slice is fine here because agent work is I/O-bound; you’re waiting on the model anyway. It would be bad for anything CPU-heavy.
Reading the deploy log
Section titled “Reading the deploy log”Render shows you the build in real time. Worth knowing what you’re looking at:
==> Cloning from https://github.com/...==> Running build command 'pip install uv && uv sync --frozen'... Resolved 94 packages in 12ms Installed 94 packages in 1.2s==> Build successful 🎉==> Deploying...==> Running 'uv run fastapi run main.py --port $PORT' INFO: Uvicorn running on http://0.0.0.0:10000==> Your service is live 🎉If it stops after “Deploying…” and eventually says something about ports, your
start command isn’t binding 0.0.0.0.
Keeping it warm
Section titled “Keeping it warm”The free tier sleeps. If you need it responsive:
- Hit
/healthbefore demos, manually. - Or schedule a GitHub Action to ping it. Note this consumes your 750 monthly instance hours faster — pinging every 10 minutes keeps it awake permanently, which is roughly 720 hours a month. That’s within budget for one service, and over it if you run two.
Don’t ping more often than you need to.
See also
Section titled “See also”- Troubleshooting — when it doesn’t work
- FastAPI Cloud — the alternative path