Skip to content

feat: add Render deployment blueprint#43

Merged
GeneralJerel merged 6 commits intomainfrom
claude/deploy-to-render
Mar 24, 2026
Merged

feat: add Render deployment blueprint#43
GeneralJerel merged 6 commits intomainfrom
claude/deploy-to-render

Conversation

@GeneralJerel
Copy link
Copy Markdown
Collaborator

Summary

  • Add render.yaml blueprint with two services: a public Next.js frontend (Docker) and a private LangGraph Python agent
  • Normalize LANGGRAPH_DEPLOYMENT_URL to handle Render's bare host:port format from fromService
  • Make MCP server opt-in via MCP_SERVER_URL env var (no hardcoded excalidraw default)

Architecture

render.yaml
  app    (web, Docker)    → public Next.js frontend
  agent  (pserv, Python)  → private LangGraph agent (internal only)

The agent uses langgraph dev --no-browser --no-reload with in-memory storage, suitable for a demo deployment.

Post-deploy steps

  1. Create Blueprint instance in Render dashboard
  2. Set OPENAI_API_KEY secret on the agent service
  3. Optionally set LANGSMITH_API_KEY and MCP_SERVER_URL

Test plan

  • pnpm --filter @repo/app build succeeds
  • Agent starts with uv run langgraph dev --host 0.0.0.0 --port 8123 --no-browser --no-reload
  • Local end-to-end: chat with agent, todos render correctly
  • Deploy to Render staging and verify service discovery wiring

🤖 Generated with Claude Code

Add render.yaml with two services: a public Docker-based Next.js
frontend and a private Python LangGraph agent. Normalize the
LANGGRAPH_DEPLOYMENT_URL to handle Render's bare host:port format,
and make MCP server configuration opt-in via env var instead of
hardcoding the excalidraw default.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@GeneralJerel
Copy link
Copy Markdown
Collaborator Author

Self-Review Findings

Verdict: Block merge — P0=1, P1=1

ID Sev File Issue Suggested Fix
F1 P0 render.yaml:9 langgraph dev is a development server ("Run LangGraph API server in development mode with hot reloading and debugging support"). langgraph up exists for production ("Launch LangGraph API server"). Switch to langgraph up, or if that requires Docker, switch the agent service to runtime: docker with a built image via langgraph build
F2 P1 render.yaml:3-9 No health check configured for the agent private service. Frontend may get 502/connection refused during startup or after crashes. Add healthCheckPath or verify Render's default TCP check is sufficient for pserv
F3 P2 render.yaml:37-40 turbo.json missing from frontend buildFilter — it's COPY'd in the Dockerfile but changes won't trigger a rebuild Add turbo.json to buildFilter paths
F4 P2 render.yaml:8 pip install uv is fragile — could break with base image changes Consider curl -LsSf https://astral.sh/uv/install.sh | sh instead
F5 P3 route.ts:37 serverId renamed from example_mcp_app to mcp_app — minor, just noting for traceability No fix needed

Open Questions

  1. Is langgraph dev intentional here, or should it be langgraph up? The up command requires Docker — does Render's Python runtime support that, or should the agent switch to runtime: docker?
  2. Does the LangGraph server expose a health check endpoint?
  3. Should any auth (e.g. COPILOTKIT_API_KEY) be configured for the public endpoint?

GeneralJerel and others added 3 commits March 24, 2026 09:21
Sliding-window rate limiter (20 req/min per IP) to prevent individual
abuse of the public CopilotKit endpoint. In-memory with periodic
cleanup to prevent unbounded Map growth. No new dependencies.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Switch agent from langgraph dev to production Docker image (langchain/langgraph-api)
- Add health check endpoint (/ok) for agent private service
- Add turbo.json to frontend buildFilter to prevent stale builds
- Add Dockerfile.agent for production agent builds
- Revert serverId to example_mcp_app for traceability
- LLM_MODEL env var in agent (defaults to gpt-5.4-2026-03-05)
- RATE_LIMIT_WINDOW_MS and RATE_LIMIT_MAX env vars (defaults 60s/40 req)
- README callout: strong models required for generative UI (GPT-5.4,
  Claude Opus 4.6, Gemini 3.1 Pro)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator Author

@GeneralJerel GeneralJerel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review

Overall this is well-scoped and production-oriented. A few issues to address before deploying:

Blocker

Missing docker/Dockerfile.apprender.yaml line 25 references dockerfilePath: docker/Dockerfile.app but this file is not included in the PR or the repo. The frontend service will fail to build on Render.

Verify Before Deploy

  1. LANGSERVE_GRAPHS export namedocker/Dockerfile.agent line 13 sets LANGSERVE_GRAPHS='{"sample_agent": "/deps/agent/main.py:graph"}', but main.py exports agent, not graph. If langgraph-api expects a graph attribute, this will fail at runtime.

  2. Agent health check /okrender.yaml line 8 configures healthCheckPath: /ok. Does langchain/langgraph-api serve this endpoint out of the box? If not, the agent will never pass health checks and Render will keep restarting it.

Minor

  • Rate limiter in serverless — The setInterval cleanup and in-memory Map in route.ts work fine for the Render Docker deployment (long-lived process), but are effectively a no-op if someone runs on Vercel/serverless (ephemeral process, lost on cold start). Worth a comment noting this.

  • .env.example — Missing trailing newline (POSIX convention).

Positives

  • MCP opt-in via env var instead of hardcoding excalidraw — good call
  • URL normalization for Render's bare host:port format is clean
  • buildFilter correctly scoped per service
  • Simple no-dependency rate limiter with cleanup is appropriate for a demo

In-memory rate limiting doesn't scale across multiple instances for
high-traffic deployments. Disable by default via RATE_LIMIT_ENABLED
env var so it doesn't silently misbehave at scale. Can be re-enabled
for single-instance or low-traffic deployments.
@GeneralJerel
Copy link
Copy Markdown
Collaborator Author

PR #43 Review: Render Deployment Blueprint

Status: Approve with minor notes

The PR is well-structured across 5 commits that incrementally add Render deployment support. The earlier self-review findings (F1–F5) have all been addressed in subsequent commits.

What's good

  • MCP opt-in — Conditional spread on MCP_SERVER_URL is clean; no more hardcoded excalidraw default
  • URL normalization (route.ts:38-44) — Handles Render's bare host:port from fromService correctly
  • Rate limiter feature-flagged — Disabled by default with RATE_LIMIT_ENABLED=false, avoiding silent misbehavior at scale
  • graph = agent alias in main.py:78 correctly matches the LANGSERVE_GRAPHS env var in the Dockerfile
  • Dockerfile.app already exists on main — the earlier review blocker about it being missing was a false alarm
  • buildFilter scoping is correct per service

Issues to consider

1. No LLM_MODEL env var passed to agent service in render.yaml (minor)

render.yaml:9-13 passes OPENAI_API_KEY and LANGSMITH_API_KEY to the agent, but not LLM_MODEL. The agent's main.py reads os.environ.get("LLM_MODEL", ...) — without it in render.yaml, operators would need to add it manually in the Render dashboard. Consider adding it with a default:

- key: LLM_MODEL
  value: gpt-5.4-2026-03-05

2. Rate limiter env vars not in render.yaml either (minor)

Same as above — RATE_LIMIT_ENABLED, RATE_LIMIT_WINDOW_MS, RATE_LIMIT_MAX are documented in .env.example but not wired through render.yaml for the frontend service. Since it's disabled by default this is fine, just noting it.

3. setInterval in module scope (route.ts:27-35)

The interval is correctly guarded by RATE_LIMIT_ENABLED, so it won't fire when disabled. For the Docker deployment on Render (long-lived process), this works. Just be aware this is a no-op on Vercel/serverless — the existing comment on line 11 covers this.

4. Next.js standalone output assumption

Dockerfile.app:49 copies .next/standalone — make sure next.config has output: "standalone" configured. This isn't part of this PR but is a dependency for the Docker build to succeed.

Verdict

Good to merge. The deployment architecture is sound, previous review findings have been addressed, and the remaining items are minor configuration niceties. The LLM_MODEL env var in render.yaml would be a nice-to-have but isn't blocking since operators can add it via the dashboard.

Wire LLM_MODEL to the agent service and rate limiter env vars to the
frontend service so operators don't need to configure them manually
in the Render dashboard.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fi; \
done

ENV LANGSERVE_GRAPHS='{"sample_agent": "/deps/agent/main.py:graph"}'
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The langgraph-api base image expects the graph object to be importable, but there's no verification that it can actually resolve /deps/agent/main.py:graph at runtime. Worth a quick smoke test before deploying:

docker build -f docker/Dockerfile.agent . && docker run --rm <image> python -c "from main import graph; print(graph)"

@GeneralJerel GeneralJerel merged commit 4a776bd into main Mar 24, 2026
9 of 10 checks passed
@GeneralJerel GeneralJerel deleted the claude/deploy-to-render branch March 25, 2026 19:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant