-
Notifications
You must be signed in to change notification settings - Fork 0
Add Render deployment support with Lite Mode and static serving #477
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
ecc4f24
0b9da49
acae19b
33fc41f
81b363a
9d6a44b
2a3f195
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,51 @@ | ||||||||||||||||||||||
| # Deploying to Render (Free Tier) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| This guide explains how to deploy the Email Intelligence Platform to Render's Free Tier (512MB RAM, 0.1 CPU). | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ## Optimized Architecture | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| To fit within the free tier limits, this deployment uses: | ||||||||||||||||||||||
| * **Single Service**: The Python backend serves the built React frontend as static files. | ||||||||||||||||||||||
| * **Lite Mode**: Heavy AI libraries (PyTorch, Transformers, Scikit-learn) are **disabled** to save memory. | ||||||||||||||||||||||
| * **Ephemeral Storage**: Data is stored in local JSON files which **will be lost** on restart/redeploy. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ## Deployment Steps | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| 1. **Create a New Web Service** on [Render Dashboard](https://dashboard.render.com/). | ||||||||||||||||||||||
| 2. Connect your GitHub repository. | ||||||||||||||||||||||
| 3. Select **"Manual Deployment"** (if prompted) or proceed to configuration. | ||||||||||||||||||||||
| 4. Configure the service details: | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| * **Name**: `email-intelligence` (or your choice) | ||||||||||||||||||||||
| * **Region**: Any (e.g., Oregon) | ||||||||||||||||||||||
| * **Branch**: `main` (or your working branch) | ||||||||||||||||||||||
| * **Root Directory**: `.` (leave empty) | ||||||||||||||||||||||
| * **Runtime**: **Python 3** | ||||||||||||||||||||||
| * **Build Command**: `./setup_env.sh` | ||||||||||||||||||||||
| * **Start Command**: `uvicorn src.backend.python_backend.main:app --host 0.0.0.0 --port $PORT` | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Check for src/main.py
echo "=== Checking src/main.py ==="
if [ -f "src/main.py" ]; then
echo "File exists"
echo "Content:"
cat -n src/main.py
else
echo "File does not exist"
fi
echo -e "\n=== Checking src/backend/python_backend/main.py ==="
if [ -f "src/backend/python_backend/main.py" ]; then
echo "File exists"
echo "Content:"
cat -n src/backend/python_backend/main.py
else
echo "File does not exist"
fi
echo -e "\n=== Searching for main.py files under src/ ==="
fd "main.py" src/ --type f 2>/dev/null || echo "No main.py files found under src/"Repository: MasumRab/EmailIntelligence Length of output: 50382 🏁 Script executed: cat -n DEPLOY_RENDER.mdRepository: MasumRab/EmailIntelligence Length of output: 2736 Update start command to reference the current entry point instead of a deprecated module The start command on line 25 references Using the deprecated module means the deployment will break when that module is removed in a future release. 🤖 Prompt for AI Agents |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| 5. **Environment Variables**: | ||||||||||||||||||||||
| Add the following environment variables: | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| | Key | Value | Description | | ||||||||||||||||||||||
| | :--- | :--- | :--- | | ||||||||||||||||||||||
| | `RENDER` | `true` | Activates "Lite Mode" and triggers frontend build. | | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Render sets this value to 🤖 Prompt for AI Agents |
||||||||||||||||||||||
| | `PYTHON_VERSION` | `3.11.0` | Ensures correct Python version. | | ||||||||||||||||||||||
| | `NODE_VERSION` | `20.0.0` | Ensures correct Node.js version for frontend build. | | ||||||||||||||||||||||
| | `PORT` | `10000` | (Render sets this automatically, but good to know). | | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| 6. **Create Web Service**. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ## Verification | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Once deployed: | ||||||||||||||||||||||
| * Navigate to your Render URL (e.g., `https://email-intelligence.onrender.com`). | ||||||||||||||||||||||
| * The React frontend should load. | ||||||||||||||||||||||
| * The `/api/health` (if exists) or other API endpoints should work. | ||||||||||||||||||||||
| * **Note**: Local AI features will be disabled. | ||||||||||||||||||||||
|
Comment on lines
+41
to
+45
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (typo): Consider rephrasing "(if exists)" to "if it exists" or "if present" for correct grammar. A possible revision: "The
Suggested change
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ## Troubleshooting | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| * **Build Failures**: Check the logs. Ensure `client/package.json` dependencies are compatible with Node 20. | ||||||||||||||||||||||
| * **Memory Issues**: If the app crashes with "Out of Memory", ensure `RENDER=true` is set. | ||||||||||||||||||||||
| * **Data Loss**: Remember that data is not persistent. For persistence, you would need to connect a managed PostgreSQL database (paid or external free tier). | ||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| import os | ||
|
|
||
| file_path = 'src/main.py' | ||
|
|
||
| with open(file_path, 'r') as f: | ||
| lines = f.readlines() | ||
|
|
||
| new_logic = """ # Catch-all for SPA routing (excluding API and UI) | ||
| @app.get("/{full_path:path}") | ||
| async def catch_all(full_path: str): | ||
| if full_path.startswith("api") or full_path.startswith("ui"): | ||
| raise HTTPException(status_code=404, detail="Not found") | ||
|
|
||
| # Sanitize and validate path to prevent directory traversal | ||
| clean_path = os.path.normpath(full_path).lstrip('/') | ||
| file_path = os.path.join(static_dir, clean_path) | ||
|
|
||
| # Ensure file_path is within static_dir | ||
| if not os.path.commonpath([file_path, static_dir]) == static_dir: | ||
| raise HTTPException(status_code=403, detail="Access denied") | ||
|
|
||
| # Check if file exists in static dir | ||
| if os.path.exists(file_path) and os.path.isfile(file_path): | ||
| return FileResponse(file_path) | ||
|
|
||
| # Otherwise return index.html | ||
| return FileResponse(os.path.join(static_dir, "index.html")) | ||
| """ | ||
|
|
||
| # Find the catch_all function block to replace | ||
| start_idx = -1 | ||
| end_idx = -1 | ||
|
|
||
| for i, line in enumerate(lines): | ||
| if '@app.get("/{full_path:path}")' in line: | ||
| start_idx = i | ||
| if start_idx != -1 and 'return FileResponse(os.path.join(static_dir, "index.html"))' in line: | ||
| end_idx = i | ||
| break | ||
|
|
||
| if start_idx != -1 and end_idx != -1: | ||
| # Remove old block | ||
| del lines[start_idx:end_idx+1] | ||
| # Insert new block | ||
| lines.insert(start_idx, new_logic) | ||
|
|
||
| with open(file_path, 'w') as f: | ||
| f.writelines(lines) | ||
| print("Fixed path traversal in src/main.py") | ||
| else: | ||
| print("Could not find catch_all block") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same
secretscontext issue as ingemini-invoke.ymlThis
ifcondition referencessecrets.GEMINI_API_KEYandsecrets.GOOGLE_API_KEY, which are not available in step-levelifexpressions forworkflow_call-triggered workflows. Apply the same workaround (e.g., a prior step that checks credentials viaenvand sets an output).🧰 Tools
🪛 actionlint (1.7.11)
[error] 45-45: context "secrets" is not allowed here. available contexts are "env", "github", "inputs", "job", "matrix", "needs", "runner", "steps", "strategy", "vars". see https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability for more details
(expression)
[error] 45-45: context "secrets" is not allowed here. available contexts are "env", "github", "inputs", "job", "matrix", "needs", "runner", "steps", "strategy", "vars". see https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability for more details
(expression)
🤖 Prompt for AI Agents