← Back to dashboard

Render Integration Guide

What is Render?

The real model

Containerised, always-on services with persistent disks and managed Postgres.

Render fills the niche between "serverless" and "run your own k8s." Services run continuously (no scale-to-zero on paid tiers), keep WebSocket connections, and can attach persistent disks for state Postgres won't hold (uploads, SQLite). The managed Postgres is solid and isn't artificially limited like Heroku's. Background Workers are first-class — a worker is just a service with no port. Cron Jobs run on a schedule with full app context. For codeAmani, Render is the option when Vercel's serverless shape doesn't fit — Long-poll websockets, audio streaming agents, scheduled scrapers.

Five Render primitives

Long-running services with Heroku ergonomics and no vendor lockin.

Text
██████╗ ███████╗███╗   ██╗██████╗ ███████╗██████╗
██╔══██╗██╔════╝████╗  ██║██╔══██╗██╔════╝██╔══██╗
██████╔╝█████╗  ██╔██╗ ██║██║  ██║█████╗  ██████╔╝
██╔══██╗██╔══╝  ██║╚██╗██║██║  ██║██╔══╝  ██╔══██╗
██║  ██║███████╗██║ ╚████║██████╔╝███████╗██║  ██║
╚═╝  ╚═╝╚══════╝╚═╝  ╚═══╝╚═════╝ ╚══════╝╚═╝  ╚═╝

Render Integration Guide

Focus: Managing Render cloud infrastructure from Claude Code using the official Render MCP server and REST API automation.

Overview

Render is a unified cloud platform for deploying web services, private services, static sites, background workers, cron jobs, and managed Postgres / Key Value (Redis-compatible) datastores. The official Render MCP server (GA on 2025-08-21) lets Claude Code inspect services, query databases, fetch logs, analyze metrics, trigger deploys, and create new resources — all through natural language without leaving your session.

Here is the big picture — a single git push fans out into all of Render's service types, so you can reason about the whole platform at a glance:

Official Documentation


MCP Server Setup

Render hosts an official MCP server at https://mcp.render.com/mcp. Authenticate with OAuth (recommended, browser sign-in) or with a Render API key (for non-interactive environments). The server is open source at github.com/render-oss/render-mcp-server; prefer the hosted endpoint over running it locally so it stays current as new tools ship.

You are about to give Claude Code a direct line into your infrastructure — here is how a natural-language request flows through the MCP server to your live services:

Bash
# OAuth (recommended) — registers the server, then you authorize in the browser
claude mcp add --transport http --client-id claude render https://mcp.render.com/mcp
# then run /mcp inside Claude Code → select "render" → Authenticate

# API key (non-interactive) — Bearer token instead of OAuth
claude mcp add --transport http render \
  https://mcp.render.com/mcp \
  --header "Authorization: Bearer ${RENDER_API_KEY}"

.mcp.json Configuration (API-key auth)

JSON
{
  "mcpServers": {
    "render": {
      "type": "http",
      "url": "https://mcp.render.com/mcp",
      "headers": {
        "Authorization": "Bearer ${RENDER_API_KEY}"
      }
    }
  }
}

Get your API key from: https://dashboard.render.com/u/settings → API Keys. After connecting, set the active workspace once per session — prompt "Set my Render workspace to <name>"; every tool call is scoped to that workspace.

Available MCP Tools (by resource)

ResourceActions
Workspaceslist workspaces · set current workspace · get current workspace details
Servicescreate (web service · static site · cron job · Postgres · Key Value) · list · get details · update all env vars
Deploystrigger a deploy (optionally clearing build cache) · list deploy history · get a deploy
Logslist logs by filter · list values for a log label
MetricsCPU / memory · instance count · datastore connection counts · response counts by status code · response times (Pro workspace+) · outbound bandwidth
Render Postgrescreate · list · get · run a read-only SQL query
Render Key Valuecreate · list · get

What the MCP server can't do. It creates only web services, static sites, cron jobs, Postgres, and Key Value — not background workers, private services, or image-backed services, and it can't set IP allowlists. For existing services it only triggers deploys and updates env vars; it does not modify scaling settings or other operational controls. Use render.yaml or the REST API for those.


Render CLI

Render ships an official CLI (render, GA — v2.24.0 at review time; source at github.com/render-oss/cli). It complements the MCP server for terminal and CI/CD work — triggering deploys, tailing logs, opening a psql or SSH session, and validating Blueprints.

Bash
# Install (macOS/Linux)
brew install render        # or: curl -fsSL https://raw.githubusercontent.com/render-oss/cli/refs/heads/main/bin/install.sh | sh

render login               # browser CLI-token auth; then pick an active workspace
render workspace set       # switch the active workspace at any time
CommandDoes
render servicesList services/datastores in the active workspace (interactive menu)
render deploys create [SERVICE_ID]Trigger a deploy — --wait, --commit <sha>, --image <tag>
render deploys list [SERVICE_ID]Deploy history for a service
render psql [DATABASE_ID]Open psql; -c "SQL" runs one query and exits
render ssh [SERVICE_ID]SSH into a running instance; --ephemeral for an isolated shell
render blueprints validate [FILE]Validate a render.yaml (defaults to ./render.yaml)
render skills [install|list]Install Render agent skills for Claude Code / Codex / Cursor

For CI/CD, authenticate non-interactively with RENDER_API_KEY (takes precedence over CLI tokens) and pass -o json + --confirm:

Bash
export RENDER_API_KEY=rnd_...
render deploys create "$RENDER_SERVICE_ID" --output json --confirm --wait

REST API Integration

The REST API is the lowest-level programmatic interface, underneath both the CLI and the MCP server — reach for it when you need a field the CLI/MCP don't expose (e.g. creating background workers or private services).

Get your services

Bash
curl https://api.render.com/v1/services \
  -H "Authorization: Bearer $RENDER_API_KEY" \
  -H "Content-Type: application/json" | jq '.[] | {id, name, status}'

Trigger a manual deploy

Bash
SERVICE_ID="srv-..."
curl -X POST "https://api.render.com/v1/services/$SERVICE_ID/deploys" \
  -H "Authorization: Bearer $RENDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"clearCache": "do_not_clear"}'

Create a web service (TypeScript)

TypeScript
const response = await fetch("https://api.render.com/v1/services", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RENDER_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    // type enum: web_service | static_site | private_service | background_worker | cron_job
    type: "web_service",
    name: "my-api",
    ownerId: "usr-...",           // workspace/owner ID (usr-… or tea-…)
    repo: "https://github.com/my-org/my-repo", // repo is a plain URL string
    branch: "main",              // top-level; defaults to the repo's default branch
    autoDeploy: "yes",           // "yes" | "no"
    serviceDetails: {
      runtime: "node",           // field is "runtime" (docker|elixir|go|node|python|ruby|rust|image)
      plan: "starter",           // starter|standard|pro|pro_plus|pro_max|pro_ultra|free
      region: "oregon",
      // build/start commands live under envSpecificDetails for native runtimes
      envSpecificDetails: {
        buildCommand: "npm ci && npm run build",
        startCommand: "node dist/index.js",
      },
    },
  }),
});

const service = await response.json();
console.log("Created:", service.service.serviceDetails.url);

Python helper for Render API

Python
import os, requests

RENDER_API_KEY = os.environ["RENDER_API_KEY"]
BASE = "https://api.render.com/v1"
HEADERS = {"Authorization": f"Bearer {RENDER_API_KEY}", "Content-Type": "application/json"}

def get_services():
    return requests.get(f"{BASE}/services", headers=HEADERS).json()

def get_logs(service_id: str, limit: int = 100):
    return requests.get(
        f"{BASE}/services/{service_id}/logs",
        headers=HEADERS,
        params={"limit": limit},
    ).json()

def trigger_deploy(service_id: str):
    return requests.post(
        f"{BASE}/services/{service_id}/deploys",
        headers=HEADERS,
        json={"clearCache": "do_not_clear"},
    ).json()

render.yaml Blueprint (Infrastructure as Code)

A Blueprint (render.yaml at your repo root) is the single source of truth for an interconnected set of services, databases, and environment groups. Commit it to Git, connect the repo in the Dashboard, and Render provisions everything in one pass. By default Render re-syncs affected resources on every push to the linked branch, so you manage infra the same way you manage code — via PRs and git push.

A web service + managed Postgres + a shared env group, fully wired:

YAML
# render.yaml
services:
  - type: web
    name: amani-api
    runtime: node
    plan: starter
    region: oregon
    buildCommand: npm ci && npm run build
    startCommand: node dist/index.js
    autoDeployTrigger: commit
    envVarGroups:
      - amani-shared
    envVars:
      # Wire the DATABASE_URL straight from the managed database below
      - key: DATABASE_URL
        fromDatabase:
          name: amani-db
          property: connectionString
      # Prompt for this secret once during Blueprint creation (never stored in Git)
      - key: RENDER_API_KEY
        sync: false
      # Let Render generate a strong random secret
      - key: SESSION_SECRET
        generateValue: true

databases:
  - name: amani-db
    plan: basic-256mb
    databaseName: amani
    user: amani
    region: oregon
    postgresMajorVersion: "17"

envVarGroups:
  - name: amani-shared
    envVars:
      - key: NODE_ENV
        value: production
      - key: TZ
        value: Africa/Nairobi

Gotcha — sync overwrites, but never deletes. Dashboard edits to a Blueprint-managed resource are overwritten on the next sync if they conflict with the YAML, so make changes in render.yaml, not the UI. Conversely, removing a resource from the file does not delete it — syncing never deletes existing resources, so you must delete them manually in the Dashboard. Also never manage one resource from two Blueprints, and list all fields when importing an existing resource (omitted fields fall back to defaults that likely differ from your current setup).


Environment Variables

Bash
# Required
RENDER_API_KEY=rnd_...          # From dashboard.render.com → Settings → API Keys

# Your service variables (set via dashboard or API)
DATABASE_URL=postgresql://...
PORT=10000                       # Render injects PORT automatically

Set environment variables via API:

Bash
curl -X PUT "https://api.render.com/v1/services/$SERVICE_ID/env-vars" \
  -H "Authorization: Bearer $RENDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '[{"key":"MY_VAR","value":"my-value"}]'

Automation Workflows

Claude Code Slash Command: Service Health Check

.claude/commands/render-health.md:

Markdown
Check the health of all Render services.

Use the Render MCP tool `list_services` to get all services and their status. 
For any service that is NOT "live", use `get_logs` to fetch recent logs and diagnose the issue.
Provide a summary table of service name, status, and any detected errors.

Usage: /project:render-health

GitHub Actions: Deploy after Tests Pass

YAML
# .github/workflows/render-deploy.yml
name: Deploy to Render
on:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '22' }
      - run: npm ci && npm test

  deploy:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - name: Trigger Render deploy
        env:
          RENDER_API_KEY: ${{ secrets.RENDER_API_KEY }}
          SERVICE_ID: ${{ secrets.RENDER_SERVICE_ID }}
        run: |
          curl -X POST "https://api.render.com/v1/services/$SERVICE_ID/deploys" \
            -H "Authorization: Bearer $RENDER_API_KEY" \
            -H "Content-Type: application/json" \
            -d '{"clearCache":"do_not_clear"}'

      - name: Wait for deploy and check status
        env:
          RENDER_API_KEY: ${{ secrets.RENDER_API_KEY }}
          SERVICE_ID: ${{ secrets.RENDER_SERVICE_ID }}
        run: |
          for i in {1..20}; do
            status=$(curl -s "https://api.render.com/v1/services/$SERVICE_ID/deploys?limit=1" \
              -H "Authorization: Bearer $RENDER_API_KEY" | jq -r '.[0].deploy.status')
            echo "Status: $status"
            if [ "$status" = "live" ]; then echo "Deploy succeeded!"; exit 0; fi
            if [ "$status" = "deactivated" ]; then echo "Deploy failed!"; exit 1; fi
            sleep 15
          done
          echo "Timeout waiting for deploy"; exit 1

Common Use Cases

Use CaseApproach
Inspect failing serviceMCP logs + metrics tools
Query production DBMCP read-only SQL query on a Postgres database
Create new serviceMCP create (web / static / cron / Postgres / Key Value) or REST API POST
Create a worker / private serviceREST API POST or render.yaml (not supported by MCP)
Deploy on mergeGitHub Actions + REST API deploys endpoint, or MCP trigger-deploy
Env var managementMCP update-env-vars or REST API PUT
Monitor resource usageMCP metrics tools

Troubleshooting

IssueFix
Service stuck in "building"Check get_logs for build errors
Port connection refusedEnsure app listens on process.env.PORT
503 on requestsService may be suspended (free tier)
Deploy not triggeringRender auto-deploys on git push — check webhook in dashboard
API key invalidGenerate a new key in dashboard → Settings → API Keys
Database connection failedCheck DATABASE_URL env var; allow external connections in DB settings