Render deploys any stack with zero infrastructure ops — render.yaml at the repo root declares Web Services, Private Services, Background Workers, Cron Jobs, and managed Databases as code. Web Service: type: web, env: node, buildCommand: npm run build, startCommand: npm start, healthCheckPath: /api/health. Environment variables: envVars list with key and value or fromDatabase: name: mydb, property: connectionString. Managed Postgres: type: pserv (or in dashboard) — Render provisions and patches the DB automatically. Redis: type: redis. Private Services (type: pserv) are reachable only inside Render’s network via MY_SERVICE_HOST:PORT. Background Workers: type: worker, no inbound port, runs continuously. Cron Jobs: type: cron, schedule: "0 8 * * *". Preview environments: automatic per PR, with their own databases if configured. Custom domains: add in dashboard, Render auto-provisions TLS via Let’s Encrypt. Disks: disk: name: data, mountPath: /data, sizeGB: 10 for persistent storage. Zero-downtime deploys: health check passes before traffic switches. render CLI: render deploy triggers a deploy. Claude Code generates render.yaml files, multi-service configs, and GitHub Actions workflows for Render.
CLAUDE.md for Render
## Render Stack
- Config: render.yaml at repo root — declarative Infrastructure as Code
- Web: type: web, env: node, buildCommand: npm run build, startCommand: npm start
- Health check: healthCheckPath: /api/health — required for zero-downtime deploys
- Env vars: envVars list with key/value, or fromDatabase: { name, property: connectionString }
- Database refs: ${{ DATABASE_URL }} in envVars references the connectionString property
- Postgres: add database to render.yaml with type: not set (dashboard) or reference existing
- Private service: type: pserv — only reachable inside Render network
- Cron: type: cron, schedule: "0 8 * * *", command: "npm run cron:daily"
render.yaml — Full Stack Configuration
# render.yaml — full-stack Next.js app with Postgres and Redis
services:
# ── Web Application ────────────────────────────────────────────────
- type: web
name: myapp-web
runtime: node
plan: starter
buildCommand: npm ci && npm run build
startCommand: npm start
healthCheckPath: /api/health
autoDeploy: true
envVars:
- key: NODE_ENV
value: production
- key: DATABASE_URL
fromDatabase:
name: myapp-db
property: connectionString
- key: REDIS_URL
fromService:
type: redis
name: myapp-redis
property: connectionString
- key: NEXTAUTH_URL
value: https://myapp.onrender.com
- key: NEXTAUTH_SECRET
generateValue: true # Render auto-generates a secure random value
# ── Background Worker ──────────────────────────────────────────────
- type: worker
name: myapp-worker
runtime: node
buildCommand: npm ci && npm run build:worker
startCommand: node dist/worker.js
envVars:
- key: DATABASE_URL
fromDatabase:
name: myapp-db
property: connectionString
- key: REDIS_URL
fromService:
type: redis
name: myapp-redis
property: connectionString
# ── Cron Job ───────────────────────────────────────────────────────
- type: cron
name: myapp-daily-report
runtime: node
schedule: "0 8 * * *" # 8am UTC daily
buildCommand: npm ci && npm run build
startCommand: node dist/cron/daily-report.js
envVars:
- key: DATABASE_URL
fromDatabase:
name: myapp-db
property: connectionString
# ── Redis ─────────────────────────────────────────────────────────
- type: redis
name: myapp-redis
plan: starter
ipAllowList: [] # private — only accessible from Render services
databases:
# ── Postgres ──────────────────────────────────────────────────────
- name: myapp-db
plan: starter
databaseName: myapp
user: myapp_user
Health Check Endpoint
// app/api/health/route.ts — Render health check (required for zero-downtime)
import { NextResponse } from "next/server"
import { db } from "@/lib/db"
export const dynamic = "force-dynamic"
export async function GET() {
const checks: Record<string, "ok" | "error"> = {}
try {
await db.$queryRaw`SELECT 1`
checks.database = "ok"
} catch {
checks.database = "error"
}
const healthy = Object.values(checks).every((v) => v === "ok")
return NextResponse.json(
{
status: healthy ? "ok" : "degraded",
checks,
timestamp: new Date().toISOString(),
},
{ status: healthy ? 200 : 503 },
)
}
Environment Configuration
// lib/config.ts — Render environment variables with validation
import { z } from "zod"
const Schema = z.object({
// Injected by Render automatically
RENDER: z.string().optional(), // "true" when running on Render
RENDER_SERVICE_NAME: z.string().optional(), // service name
RENDER_GIT_COMMIT: z.string().optional(), // git SHA
RENDER_EXTERNAL_URL: z.string().url().optional(), // public URL of this service
// Database + Cache — injected from fromDatabase / fromService
DATABASE_URL: z.string().url(),
REDIS_URL: z.string().url().optional(),
// App config
NEXTAUTH_URL: z.string().url(),
NEXTAUTH_SECRET: z.string().min(32),
PORT: z.coerce.number().default(10000), // Render always uses 10000
})
export const config = Schema.parse(process.env)
GitHub Actions Deploy Trigger
# .github/workflows/deploy.yml — trigger Render deploy via API
name: Deploy to Render
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Trigger Render Deploy
run: |
curl -s -X POST \
-H "Authorization: Bearer ${{ secrets.RENDER_API_KEY }}" \
-H "Content-Type: application/json" \
-d '{"clearCache": false}' \
"https://api.render.com/v1/services/${{ secrets.RENDER_SERVICE_ID }}/deploys"
Prisma Database Migration on Deploy
// scripts/db-migrate.ts — run Prisma migrations as a Render pre-deploy script
// Set in render.yaml: preDeployCommand: npx tsx scripts/db-migrate.ts
import { execSync } from "child_process"
async function migrate() {
console.log("Running Prisma migrations...")
execSync("npx prisma migrate deploy", { stdio: "inherit" })
console.log("Migrations complete")
}
migrate().catch((err) => {
console.error("Migration failed:", err)
process.exit(1)
})
For the Railway alternative when needing a more traditional CLI-driven deployment flow with railway up, stronger GitHub PR preview environment integration, and a project-centric UI that groups related services — Railway has a more developer-CLI-centric experience while Render’s render.yaml Infrastructure-as-Code approach and automatic Let’s Encrypt TLS with custom domains make it popular for teams who want Heroku-like simplicity with a declarative config file, see the Railway guide. For the Fly.io alternative when needing Dockerfile-native deployment with anycast global edge routing, SSHable containers, persistent disks, and the Machines API for dynamic VM provisioning — Fly.io gives more infrastructure control while Render’s managed databases, preview environments, and zero-config scaling make it the simpler choice for teams who want to focus on application code, see the Fly.io guide. The Claude Skills 360 bundle includes Render skill sets covering render.yaml, multi-service setup, and automated deploys. Start with the free tier to try zero-ops deployment generation.