One-click deploy of DeepTutor — an agent-native learning workspace — as a single Docker web service on Render
Python
2
18 commits
updated Jul 14, 2026
One-click deploy of DeepTutor — an agent-native learning workspace — as a single Docker web service.
https://github.com/user-attachments/assets/7db869d6-4594-4993-b92e-3a0f5d043a85
DeepTutor connects tutoring, problem solving, quiz generation, research, visualization, and mastery practice in one extensible system (Chat, Partners, Co-Writer, Book, Knowledge Center, Memory, and more). This template deploys the all-in-one image — FastAPI backend + Next.js frontend running together under supervisord — as one Render web service backed by a persistent disk. For the full feature tour, see deeptutor.info and the upstream repo.
The render.yaml Blueprint declares every provider API key as a sync: false secret, so Render prompts for them at deploy time and stores them in its encrypted env-var store — never in the repo, never on disk.
DeepTutor reads every provider key exclusively from environment variables:
VAR_NAME) ✓/✗" indicator per provider..env.example for the full list of variable names. ┌────────────────────────────────────────────┐
│ Render web service (Docker, plan: standard) │
│ │
browser ───HTTPS──► │ Next.js frontend :3782 (health: / ) │
│ │ proxies /api and /ws in-process │
│ ▼ │
│ FastAPI backend :8001 │
│ │ │
│ ▼ │
│ /app/data (persistent disk, 10 GB) │
│ settings JSON · knowledge bases · memory │
│ workspaces · logs (NO secrets) │
└────────────────────────────────────────────┘
A single container runs both processes under supervisord. The browser talks only to the frontend origin on port 3782; Next.js middleware forwards /api/* and /ws/* to the backend in-process. The disk at /app/data holds everything that should survive a redeploy — runtime settings, knowledge bases, per-user workspaces, memory, and logs. No secret is ever stored there.
Required:
OPENAI_API_KEY). A single key covers that provider across LLM, embeddings, TTS/STT, and image generation.
standard plan (2 GB RAM) — RAG indexing and document parsing are memory-hungry, so starter may OOM.Optional (leave blank to disable):
TAVILY_API_KEY, BRAVE_API_KEY, EXA_API_KEY, SERPER_API_KEY, or PERPLEXITY_API_KEY.COHERE_API_KEY, JINA_API_KEY.MINERU_API_TOKEN, PAGEINDEX_API_KEY, LIGHTRAG_API_KEY.AUTH_PASSWORD_HASH, POCKETBASE_ADMIN_PASSWORD (also needs a one-time on-disk JSON edit to enable).You don't need any optional keys to deploy — fill them in from the Render Environment tab later. See .env.example for the complete list.
sync: false secrets. Paste at least one LLM key (e.g. OPENAI_API_KEY); leave the rest blank.render.yaml, builds the image, and creates the web service with a 10 GB disk at /app/data.Once the deploy is live, open the .onrender.com URL Render assigned.
gpt-5.6-luna for chat, text-embedding-3-small for knowledge bases). Once OPENAI_API_KEY is set you can chat immediately — no manual Settings step required.
gpt-5.6-luna and the rest of the GPT-5 family (gpt-5, gpt-5.1, gpt-5.6-*) and the o-series (o1/o3/o4) are reasoning models that only accept the default sampling temperature — DeepTutor detects these and pins temperature=1 automatically, so any configured chat temperature is ignored for them.AZURE_OPENAI_API_KEY, but a working profile also needs its non-secret endpoint (base_url), API version, and deployment name entered in Settings. (The custom / custom_anthropic providers have no fixed env var name and can't source a key from the environment — use a named provider if you need an env-only key.)No document upload or knowledge base is required — with OPENAI_API_KEY set, plain chat works immediately against the default OpenAI profile. Each step below uses features that ship with the app.
Chat (zero setup). On the landing page you'll see the greeting "What would you like to learn?" and a composer reading "How can I help you today?". Type a question and send — for example:
Explain gradient descent like I'm new to calculus, then give me one worked example.
Switch capabilities from the composer. The picker next to the composer offers the built-in modes — try one per demo:
Solve: a train leaves at 60 mph and another at 40 mph 30 min later; when do they meet?Generate a 5-question quiz on photosynthesis with an answer key.Visualize a bar chart of the planets by diameter.TAVILY_API_KEY; see Prerequisites): Research the current state of solid-state batteries.Co-Writer (built-in sample). Open Co-Writer and create a new document — it loads a bundled starter template ("DeepTutor Co-Writer") showcasing Markdown, tables, code, LaTeX, and Mermaid diagrams. Select any text and use the inline AI action to rewrite or extend it.
Each response streams live, so these all make for a quick visual demo without any file uploads or extra keys (Research aside).
If you host a public demo URL where anyone can chat against your provider key, unbounded use means runaway spend. Demo mode caps per-visitor (per-IP) usage so the demo is safe to leave open. It is off by default — local and private forks are unaffected — and only takes effect when you set DEMO=true.
Set these under the Render Environment tab (or your host environment) on the demo service:
| Variable | Default | Purpose |
|---|---|---|
DEMO | false | Master switch. Truthy: 1, true, yes, on (case-insensitive). |
DEMO_RATE_LIMIT_PER_MIN | 15 | Max spending requests per IP per minute. |
DEMO_RATE_LIMIT_PER_HOUR | 200 | Max spending requests per IP per hour. |
When a visitor exceeds a limit, HTTP requests get a 429 with a Retry-After header and chat WebSocket messages get an error frame (the socket stays open, so the next allowed message works normally).
Ephemeral history: in demo mode chat history is kept in an in-memory SQLite database and never written to disk. A shared public demo would otherwise accumulate every visitor's conversations on the instance and — since the demo has no per-visitor auth boundary — surface them in other visitors' session lists. In-memory history is cleared on restart. (Same single-process caveat as above: the in-memory store is shared within one process; the /api/v1/chat REST loop, which the web demo does not use, still persists via its JSON store and is not gated.)
What is guarded (v1): the two chat WebSocket loops (/chat, /ws) — where the key is actually spent — plus an HTTP catch-all. The health check (/) and static outputs (/api/outputs) are exempt so the app keeps loading.
Not yet guarded: secondary spenders (/question/*, /book, notebook summaries, Co-Writer, voice TTS/STT). Single-process only: rate-limit buckets live in memory per process, which is right for a single-instance demo; running multiple instances multiplies the effective limit (each holds its own buckets). A shared store (e.g. Redis) would be the upgrade path.
The Blueprint defaults to the standard web service plan plus a 10 GB persistent disk. LLM/provider usage is billed separately by each provider. Bump the plan if you hit OOM during heavy RAG indexing. See Render pricing for current rates.
| Symptom | Likely cause / fix |
|---|---|
| Service returns 502 / won't load | Confirm the web service routes to port 3782 (not 8001) in the dashboard. |
| A provider shows "Set via environment ✗" | The env var is unset or misnamed. Check the exact name in .env.example, set it under Environment, and redeploy. |
| Out-of-memory during KB indexing | Bump the service plan above standard. |
| Browser blocked by CORS calling the API from another origin | The one-click deploy needs no CORS (the browser only talks to the frontend, which proxies the API). Only if you call the backend directly from a different origin: set CORS_ORIGINS to that origin, or CORS_ALLOW_ALL=true to trust any origin on a trusted network. |
| Auth / PocketBase won't enable | The password secrets are wired via env, but enabling login or PocketBase needs a one-time edit of data/user/settings/*.json on the disk. |
The Dockerfile is multi-stage; Render builds the final stage, which is the lean production image — no Docker Target Stage override is needed.
DeepTutor is licensed under the Apache License 2.0.
18 commits
Python
68.2%
TypeScript
31.1%
One-click deploy of DeepTutor — an agent-native learning workspace — as a single Docker web service on Render
Python
2
18 commits
updated Jul 14, 2026
One-click deploy of DeepTutor — an agent-native learning workspace — as a single Docker web service.
https://github.com/user-attachments/assets/7db869d6-4594-4993-b92e-3a0f5d043a85
DeepTutor connects tutoring, problem solving, quiz generation, research, visualization, and mastery practice in one extensible system (Chat, Partners, Co-Writer, Book, Knowledge Center, Memory, and more). This template deploys the all-in-one image — FastAPI backend + Next.js frontend running together under supervisord — as one Render web service backed by a persistent disk. For the full feature tour, see deeptutor.info and the upstream repo.
The render.yaml Blueprint declares every provider API key as a sync: false secret, so Render prompts for them at deploy time and stores them in its encrypted env-var store — never in the repo, never on disk.
DeepTutor reads every provider key exclusively from environment variables:
VAR_NAME) ✓/✗" indicator per provider..env.example for the full list of variable names. ┌────────────────────────────────────────────┐
│ Render web service (Docker, plan: standard) │
│ │
browser ───HTTPS──► │ Next.js frontend :3782 (health: / ) │
│ │ proxies /api and /ws in-process │
│ ▼ │
│ FastAPI backend :8001 │
│ │ │
│ ▼ │
│ /app/data (persistent disk, 10 GB) │
│ settings JSON · knowledge bases · memory │
│ workspaces · logs (NO secrets) │
└────────────────────────────────────────────┘
A single container runs both processes under supervisord. The browser talks only to the frontend origin on port 3782; Next.js middleware forwards /api/* and /ws/* to the backend in-process. The disk at /app/data holds everything that should survive a redeploy — runtime settings, knowledge bases, per-user workspaces, memory, and logs. No secret is ever stored there.
Required:
OPENAI_API_KEY). A single key covers that provider across LLM, embeddings, TTS/STT, and image generation.
standard plan (2 GB RAM) — RAG indexing and document parsing are memory-hungry, so starter may OOM.Optional (leave blank to disable):
TAVILY_API_KEY, BRAVE_API_KEY, EXA_API_KEY, SERPER_API_KEY, or PERPLEXITY_API_KEY.COHERE_API_KEY, JINA_API_KEY.MINERU_API_TOKEN, PAGEINDEX_API_KEY, LIGHTRAG_API_KEY.AUTH_PASSWORD_HASH, POCKETBASE_ADMIN_PASSWORD (also needs a one-time on-disk JSON edit to enable).You don't need any optional keys to deploy — fill them in from the Render Environment tab later. See .env.example for the complete list.
sync: false secrets. Paste at least one LLM key (e.g. OPENAI_API_KEY); leave the rest blank.render.yaml, builds the image, and creates the web service with a 10 GB disk at /app/data.Once the deploy is live, open the .onrender.com URL Render assigned.
gpt-5.6-luna for chat, text-embedding-3-small for knowledge bases). Once OPENAI_API_KEY is set you can chat immediately — no manual Settings step required.
gpt-5.6-luna and the rest of the GPT-5 family (gpt-5, gpt-5.1, gpt-5.6-*) and the o-series (o1/o3/o4) are reasoning models that only accept the default sampling temperature — DeepTutor detects these and pins temperature=1 automatically, so any configured chat temperature is ignored for them.AZURE_OPENAI_API_KEY, but a working profile also needs its non-secret endpoint (base_url), API version, and deployment name entered in Settings. (The custom / custom_anthropic providers have no fixed env var name and can't source a key from the environment — use a named provider if you need an env-only key.)No document upload or knowledge base is required — with OPENAI_API_KEY set, plain chat works immediately against the default OpenAI profile. Each step below uses features that ship with the app.
Chat (zero setup). On the landing page you'll see the greeting "What would you like to learn?" and a composer reading "How can I help you today?". Type a question and send — for example:
Explain gradient descent like I'm new to calculus, then give me one worked example.
Switch capabilities from the composer. The picker next to the composer offers the built-in modes — try one per demo:
Solve: a train leaves at 60 mph and another at 40 mph 30 min later; when do they meet?Generate a 5-question quiz on photosynthesis with an answer key.Visualize a bar chart of the planets by diameter.TAVILY_API_KEY; see Prerequisites): Research the current state of solid-state batteries.Co-Writer (built-in sample). Open Co-Writer and create a new document — it loads a bundled starter template ("DeepTutor Co-Writer") showcasing Markdown, tables, code, LaTeX, and Mermaid diagrams. Select any text and use the inline AI action to rewrite or extend it.
Each response streams live, so these all make for a quick visual demo without any file uploads or extra keys (Research aside).
If you host a public demo URL where anyone can chat against your provider key, unbounded use means runaway spend. Demo mode caps per-visitor (per-IP) usage so the demo is safe to leave open. It is off by default — local and private forks are unaffected — and only takes effect when you set DEMO=true.
Set these under the Render Environment tab (or your host environment) on the demo service:
| Variable | Default | Purpose |
|---|---|---|
DEMO | false | Master switch. Truthy: 1, true, yes, on (case-insensitive). |
DEMO_RATE_LIMIT_PER_MIN | 15 | Max spending requests per IP per minute. |
DEMO_RATE_LIMIT_PER_HOUR | 200 | Max spending requests per IP per hour. |
When a visitor exceeds a limit, HTTP requests get a 429 with a Retry-After header and chat WebSocket messages get an error frame (the socket stays open, so the next allowed message works normally).
Ephemeral history: in demo mode chat history is kept in an in-memory SQLite database and never written to disk. A shared public demo would otherwise accumulate every visitor's conversations on the instance and — since the demo has no per-visitor auth boundary — surface them in other visitors' session lists. In-memory history is cleared on restart. (Same single-process caveat as above: the in-memory store is shared within one process; the /api/v1/chat REST loop, which the web demo does not use, still persists via its JSON store and is not gated.)
What is guarded (v1): the two chat WebSocket loops (/chat, /ws) — where the key is actually spent — plus an HTTP catch-all. The health check (/) and static outputs (/api/outputs) are exempt so the app keeps loading.
Not yet guarded: secondary spenders (/question/*, /book, notebook summaries, Co-Writer, voice TTS/STT). Single-process only: rate-limit buckets live in memory per process, which is right for a single-instance demo; running multiple instances multiplies the effective limit (each holds its own buckets). A shared store (e.g. Redis) would be the upgrade path.
The Blueprint defaults to the standard web service plan plus a 10 GB persistent disk. LLM/provider usage is billed separately by each provider. Bump the plan if you hit OOM during heavy RAG indexing. See Render pricing for current rates.
| Symptom | Likely cause / fix |
|---|---|
| Service returns 502 / won't load | Confirm the web service routes to port 3782 (not 8001) in the dashboard. |
| A provider shows "Set via environment ✗" | The env var is unset or misnamed. Check the exact name in .env.example, set it under Environment, and redeploy. |
| Out-of-memory during KB indexing | Bump the service plan above standard. |
| Browser blocked by CORS calling the API from another origin | The one-click deploy needs no CORS (the browser only talks to the frontend, which proxies the API). Only if you call the backend directly from a different origin: set CORS_ORIGINS to that origin, or CORS_ALLOW_ALL=true to trust any origin on a trusted network. |
| Auth / PocketBase won't enable | The password secrets are wired via env, but enabling login or PocketBase needs a one-time edit of data/user/settings/*.json on the disk. |
The Dockerfile is multi-stage; Render builds the final stage, which is the lean production image — no Docker Target Stage override is needed.
DeepTutor is licensed under the Apache License 2.0.
18 commits
Python
68.2%
TypeScript
31.1%