mcpbeat Sign in

Render Agent Skill

Use when deploying or fixing an app on Render — web services, background workers, cron jobs, private services, managed Postgres and Key-Value, and especially the render.yaml Blueprint. Covers deploys that fail with no open ports detected, 502s on first deploy, free-tier cold starts, and a free Postgres about to expire with its data. NOT the generic ship and release flow (that is `deployment`), NOT another PaaS such as `railway` or `fly-io`.

7k tokens
context cost
the whole folder, loaded on every use
5
files
ships runnable scripts
0
copies elsewhere
how many repositories repackaged it
105
stars on the repo
on the repository, not the skill itself

Install

one command, takes just this skill from the repository
npx skills add https://github.com/ericrisco/rsc-harness --skill render

What comes with it

17 162 bytes besides the instruction
evals/README.md
evals/cases.yaml
references/blueprint-reference.md
scripts/verify.sh

The instruction itself

13 sections, as written by the author

Render — make this repo deploy correctly, wired as code

Take any repo and make it deploy on Render (render.com) in one pass: pick the right

service type, declare everything in a version-controlled render.yaml Blueprint, bind the

port the way Render expects, and wire DATABASE_URL / secrets across services so they

survive rotation. Steer clear of the four traps that eat first deploys: the

"no open ports detected" 502, the free-tier spin-down, the 30-day free-Postgres expiry,

and the 12-hour cron kill.

pick service type → render.yaml at repo root → bind 0.0.0.0:$PORT → wire env across services → push (auto-deploy)
        │                                                                                            │
   web|worker|cron|pserv|static|keyvalue                                       full key surface → references/blueprint-reference.md

Facts here are dated to 2026-06-02 against render.com/docs. Render ships changes; if a

key or limit looks off, confirm against the live Blueprint spec before betting a deploy on it.

The one decision that decides everything: service type

Pick the type before you write a line of YAML. The type sets the billing model, whether

the process gets a public URL, and whether Render expects it to bind a port. Choosing wrong

means a worker that never starts because Render waited for a port, or a 12-hour job that dies

silently as a cron.

| The process… | Type | runtime required? | Gets a public URL? |

| --- | --- | --- | --- |

| serves HTTP/WebSocket traffic | web | yes | yes |

| runs forever, no inbound URL (queue consumer, Celery) | worker | yes | no |

| runs, does work, exits — on a schedule | cron | yes | no |

| internal-only API, reachable only inside Render's network | pserv | yes | no (internal host only) |

| pre-built static assets (SPA, docs) | web + runtime: static | yes (static) | yes |

| cache / queue / Redis-compatible store | keyvalue | no | no (internal) |

runtime enum (everything except keyvalue): node, python, docker, image,

static, go, ruby, elixir, rust.

Blueprint-first: everything lives in render.yaml

**Rule: declare every service, database, and env var group in render.yaml at the repo

root. Touch the dashboard only for sync: false secrets and one-off debugging.** Why: the

Blueprint is the reproducible, reviewable source of truth — it powers preview environments

and a clean re-deploy, while dashboard-only config is invisible state that drifts and can't

be code-reviewed.

Top-level keys: services, databases, envVarGroups, projects, previews.

Minimal correct render.yaml (annotated)

A multi-service app — a Node web service, a Python worker, a nightly cron, a Postgres db,

and a shared env group. Every load-bearing key is commented.

databases:
  - name: app-db
    plan: starter            # NOT free for anything you care about — free expires in 30 days
    postgresMajorVersion: "17"
    region: frankfurt

envVarGroups:
  - name: app-shared
    envVars:
      - key: LOG_LEVEL
        value: info
      - key: SENTRY_DSN
        sync: false          # prompt once at setup; never stored in git

services:
  - type: web
    name: api
    runtime: node
    region: frankfurt
    plan: starter            # $7/mo — avoids the free-tier 15-min spin-down on an API
    buildCommand: npm ci && npm run build
    startCommand: npm start  # MUST bind 0.0.0.0 and read $PORT — see next section
    healthCheckPath: /healthz # gates zero-downtime rollout; new instance must pass first
    preDeployCommand: npm run migrate  # runs before the new instance serves traffic
    autoDeployTrigger: commit          # commit | checksPass | off
    envVars:
      - fromGroup: app-shared
      - key: DATABASE_URL
        fromDatabase:
          name: app-db
          property: connectionString   # never hardcode the URL
      - key: SESSION_SECRET
        generateValue: true            # Render generates a random secret

  - type: worker
    name: jobs
    runtime: python
    plan: starter
    buildCommand: pip install -r requirements.txt
    startCommand: celery -A app worker -l info  # no port — workers don't bind one
    envVars:
      - fromGroup: app-shared
      - key: DATABASE_URL
        fromDatabase: { name: app-db, property: connectionString }
      - key: API_URL
        fromService:                   # reference another service
          name: api
          type: web
          property: hostport

  - type: cron
    name: nightly-cleanup
    runtime: python
    schedule: "0 3 * * *"              # required for cron; 03:00 UTC daily
    buildCommand: pip install -r requirements.txt
    startCommand: python -m app.cleanup
    envVars:
      - key: DATABASE_URL
        fromDatabase: { name: app-db, property: connectionString }

Port binding — do this first (the #1 first-deploy failure)

**Rule: a web service MUST listen on host 0.0.0.0 and read the PORT env var (Render

sets it, default 10000).** If Render detects no bound port within its window, the deploy

fails with "no open ports detected" and visitors get a 502. This is the single most

common first-deploy break — binding localhost/127.0.0.1 or a hardcoded port does it.

// Bad — binds the wrong host and ignores Render's PORT → "no open ports detected"
app.listen(3000);

// Good — bind 0.0.0.0 and honor $PORT
app.listen(process.env.PORT || 3000, "0.0.0.0");
# Bad — gunicorn on a fixed local port
gunicorn app:app --bind 127.0.0.1:8000

# Good — bind 0.0.0.0 and Render's $PORT
gunicorn app:app --bind 0.0.0.0:$PORT

Only web services need this. A worker/cron that tries to bind a port is fine but

pointless; a web service that doesn't is broken.

Wiring env vars across services

**Rule: never hardcode DATABASE_URL, REDIS_URL, or shared secrets as literal value:

strings. Reference the resource** so the value survives a rotation, recreate, or region

move, and so secrets never land in git.

| Form | Use it for |

| --- | --- |

| fromDatabase: { name, property: connectionString } | the Postgres connection string |

| fromService: { name, type, property } | another service's host/port/URL (property: hostport, host, or port) |

| fromGroup: <group-name> | pull a whole shared env var group |

| generateValue: true | a random secret Render generates and stores (session keys) |

| sync: false | a secret you type once at setup; not stored in the repo |

For a Key Value store, reference its connection string the same way you reference Postgres,

via fromService against the keyvalue service.

Migrations and zero-downtime deploys

  • preDeployCommand — runs once, before the new instance starts serving traffic. Put

migrations here, not in startCommand (a startCommand migration runs on every instance

and races under multiple replicas).

  • healthCheckPath — Render polls it on the new instance and only shifts traffic once it

passes, giving zero-downtime rollout. Point it at a route that checks real readiness.

  • autoDeployTriggercommit (deploy every push), checksPass (wait for CI status),

or off (manual / deploy-hook only).

Free-tier traps

Render's free tier is generous for hobby work and a landmine for anything you care about.

| Trap | What happens | Fix |

| --- | --- | --- |

| Free web spin-down | after 15 min of no inbound traffic the instance sleeps; next request waits 30–60s to wake | Starter at $7/mo per service |

| 750 free instance-hrs/mo | shared across the workspace; spun-down time doesn't count toward it | budget it, or pay Starter |

| Free Postgres expiry | deleted 30 days after creation (14-day grace to upgrade), all data gone; only one free Postgres per workspace | Starter Postgres ($7/mo) from day one for anything real |

| Free Key Value | no disk persistence — data is lost on restart | paid plan if you need durability |

Cron specifics

  • schedule: (standard cron expression) is required for type: cron.
  • Render guarantees at most one active run at a time — runs don't overlap.
  • A run is killed after 12 hours. Anything that can exceed that must be a worker with

its own scheduler/queue, not a cron. A long cron fails silently mid-job — partial work,

no clean error.

Scaling and disk knobs

This is the concrete Render surface, not capacity strategy (that's scaling).

services:
  - type: web
    name: api
    runtime: node
    scaling:
      minInstances: 1
      maxInstances: 4
      targetCPUPercent: 70
      targetMemoryPercent: 80   # autoscale between min/max on CPU/mem
    disk:
      name: data
      mountPath: /var/data
      sizeGB: 10                # a disk PINS the service to ONE instance — blocks horizontal scale

Use numInstances for a fixed replica count instead of scaling when you don't want

autoscaling. A persistent disk and horizontal scaling are mutually exclusive — pick one.

Regions: oregon, ohio, virginia, frankfurt, singapore. Plans: free, starter,

standard, pro, pro plus, pro max, pro ultra.

Anti-patterns

| Anti-pattern | Why it bites | Do instead |

| --- | --- | --- |

| Hardcoding a port (listen(3000)) | "no open ports detected" → deploy fails / 502 | bind 0.0.0.0 and $PORT |

| Hardcoding DATABASE_URL | breaks on rotation / db recreate | fromDatabase reference |

| A long job as a cron | killed at 12h, silent partial work | worker + its own scheduler/queue |

| Free Postgres for production | deleted 30 days after creation | Starter ($7/mo) from day one |

| Secrets as literal value: in render.yaml | leaked in git history | sync: false or generateValue: true |

| Migrations in startCommand | runs on every instance, races under replicas | preDeployCommand |

| Free web service for a real API | cold-start 502s after idle | Starter, or accept it only for a hobby toy |

| Dashboard-only config | invisible drift, no preview envs, no review | declare in render.yaml |

When to hand off

  • Schema design, queries, indexing, tuning → ../postgresdb/SKILL.md. This skill only

*provisions and connects* Render's managed Postgres.

  • Writing the Dockerfile Render consumes via runtime: dockerdocker.
  • Registrar-side DNS records for a custom domain → domains-dns. (The Render domains:

block and verification stay here.)

  • Cross-platform release strategy, promotion, rollback flow → ../deployment/SKILL.md and

ship. This skill is Render config and platform mechanics.

  • Build steps Render's native build doesn't run (custom CI) → github-actions. Render

auto-deploys on push; only reach for Actions when you genuinely need it.

  • Capacity/load strategy beyond the scaling: knobs → scaling.
  • A different PaaS → its own sibling: ../fly-io/SKILL.md, railway, vercel,

../netlify/SKILL.md, coolify, digitalocean.

Full key surface

The exhaustive render.yaml key tables per service type, all database keys

(postgresMajorVersion, diskSizeGB, readReplicas, highAvailability, previewPlan),

every env-var reference form, region/plan enums, and four complete copy-paste recipes

(Next.js web+pg; FastAPI web+worker+cron+keyvalue; Docker monorepo with rootDir; static

SPA with routes rewrites) live in references/blueprint-reference.md — pull it open when

you need a key this body didn't cover.

How to use it

Copy the folder

Take ericrisco/render from the repository into ~/.claude/skills for personal use, or into .claude/skills inside a project.

Check the name does not clash

The agent identifies a skill by the name field in its header. Two skills with the same name cannot sit side by side — one of them will be ignored.

Install what it needs

The instructions reference pip. Without those the skill loads but fails at the first command.