Skip to content

Render

A platform-as-a-service for always-on containers — the counterpart to Vercel's serverless model. Push to git, Render builds a container and runs it as a long-lived process.

Pages

  1. Deploy a Web Service
  2. Static Sites
  3. Environment Variables

1. What Render Is

Service typeWhat it is
Web ServiceA long-running HTTP server (Node, Python, Go, Docker)
Static SiteBuilt assets on a CDN
Private ServiceAn internal HTTP service, not exposed publicly
Background WorkerA process with no HTTP port — queue consumers
Cron JobA container that runs on a schedule and exits
PostgresManaged database with automated backups
Key ValueManaged Redis-compatible store

The important difference from Vercel: your process stays running. It holds state in memory, keeps database connections open, and can serve WebSockets.


2. Render vs Vercel

RenderVercel
ModelLong-running containerServerless functions + CDN
Process lifetimeAlways onPer request
WebSocketsYesNo
Background workersYesNo (cron only)
Cron jobsYesYes
Managed Postgres / RedisBuilt inPartner integrations
Connection poolingStraightforward — one process, one poolNeeds an external pooler
Cold startsOnly on the free tierYes
Next.js supportGoodBest in class
Global edgeSingle region per serviceGlobal by default
PricingMostly flat per serviceUsage-based

The Common Architecture

Next.js frontend on Vercel + Node API and Postgres on Render. Best frontend DX, and a normal always-on backend sitting next to its database.

When Render Is the Right Choice

  • WebSockets — chat, live collaboration, notifications
  • Background job workers consuming a queue
  • Long-running requests (reports, exports, video processing)
  • You want a managed database next to your app, one bill, one dashboard
  • Predictable flat pricing rather than usage-based
  • Stateful in-memory work that serverless makes awkward

3. Free Tier Caveats

Worth knowing, because it surprises people:

  • Free web services spin down after ~15 minutes of inactivity. The next request takes 30–60 seconds to wake the container
  • Free Postgres instances expire after a limited period
  • Limited build minutes and bandwidth

The spin-down makes the free tier unsuitable for anything a real user might hit, but fine for a demo or a portfolio project you can warm up before showing.

Do Not "Fix" It With a Pinger

Hitting your own free service every 10 minutes to keep it awake defeats the purpose of the tier and is against the spirit of the terms. If it must stay up, pay for the smallest paid instance.


4. Infrastructure as Code — render.yaml

yaml
services:
  - type: web
    name: api
    runtime: node
    region: oregon
    plan: starter
    buildCommand: npm ci && npm run build
    startCommand: npm start
    healthCheckPath: /health
    autoDeploy: true
    envVars:
      - key: NODE_ENV
        value: production
      - key: DATABASE_URL
        fromDatabase:
          name: app-db
          property: connectionString
      - key: JWT_SECRET
        generateValue: true          # Render generates a random value once
      - key: REDIS_URL
        fromService:
          type: keyvalue
          name: app-cache
          property: connectionString

  - type: worker
    name: job-worker
    runtime: node
    buildCommand: npm ci && npm run build
    startCommand: npm run worker
    envVars:
      - key: DATABASE_URL
        fromDatabase:
          name: app-db
          property: connectionString

  - type: cron
    name: nightly-cleanup
    runtime: node
    schedule: "0 3 * * *"
    buildCommand: npm ci
    startCommand: node scripts/cleanup.js

databases:
  - name: app-db
    plan: basic-256mb
    postgresMajorVersion: "16"

Why This Matters in an Interview

A render.yaml (a "Blueprint") is infrastructure as code: the whole stack is versioned in git, reviewable in a PR, and reproducible. Clicking things in a dashboard is not reproducible and nobody can review it.

Note fromDatabase and fromService — Render injects connection strings automatically, so credentials never appear in the file. And generateValue: true creates a strong random secret once, without you inventing or committing one.


5. The Deploy Lifecycle

git push
  → Render detects the change
  → runs buildCommand in a build container
  → builds an image
  → starts a new instance with startCommand
  → waits for healthCheckPath to return 200
  → shifts traffic to the new instance
  → shuts down the old instance (SIGTERM)

Zero-Downtime Requires Two Things From You

1. A working health check. Render will not route traffic to an instance whose healthCheckPath fails, and will not kill the old one. Without it, traffic can hit a container that is still booting.

2. Graceful shutdown. Render sends SIGTERM to the old instance. Handle it, stop accepting new connections, finish in-flight requests, then exit. Otherwise every deploy drops requests.

js
process.on("SIGTERM", () => {
  server.close(async () => {
    await db.$disconnect();
    process.exit(0);
  });
  setTimeout(() => process.exit(1), 10_000).unref();
});

These two points are exactly what "how do you do zero-downtime deploys" is asking about.


6. Health Checks

js
app.get("/health", (req, res) => res.json({ status: "ok" }));   // liveness

app.get("/ready", async (req, res) => {                          // readiness
  try {
    await db.$queryRaw`SELECT 1`;
    res.json({ status: "ready" });
  } catch {
    res.status(503).json({ status: "not ready" });
  }
});

Which One To Point Render At

Use the liveness endpoint (/health) for healthCheckPath, not the readiness one.

If your health check queries the database and the database has a brief hiccup, Render marks a perfectly healthy container unhealthy and restarts it — during an outage, when restarting helps nothing and loses your warm state.

Keep the dependency-checking endpoint separate, for your monitoring to alert on.

Getting this distinction right is a strong signal. Most people wire the database check straight into the health endpoint and create a restart loop the first time the database blips.


7. Databases

Connection Strings

Use
InternalFrom another Render service in the same region — faster, free, not exposed to the internet
ExternalFrom your laptop or outside Render — goes over the public internet

Always use the internal URL for service-to-service traffic. It avoids egress, avoids public exposure, and is lower latency.

Backups

Paid Postgres plans include automated daily backups and point-in-time recovery.

Test the restore. A backup you have never restored is a hypothesis, not a backup. Saying this unprompted lands well — plenty of teams discover their backups were broken only when they needed them.

Connection Pooling on Render

Simpler than serverless. One long-running process means one pool, reused across all requests.

js
const pool = new Pool({ connectionString: process.env.DATABASE_URL, max: 10 });

Still size it correctly: max × number of instances must stay under the plan's connection limit. Three instances × max: 20 = 60 connections; check that against your plan.


8. Scaling

Vertical

Bigger instance — more CPU and RAM. Simplest, and often enough.

Horizontal

More instances behind Render's load balancer.

The prerequisite: your app must be stateless.

In-memory thingMove it to
SessionsRedis (Render Key Value)
CacheRedis
Rate-limit countersRedis
Uploaded filesS3 / R2
Scheduled workA cron service, not setInterval in the web process

The last one catches people: setInterval in a web service with three instances runs the job three times. Use a Cron Job service, or a distributed lock.

Autoscaling

Paid plans can scale on CPU and memory targets. Set a sensible maximum — autoscaling with no ceiling turns a traffic spike, or a bug, into a large bill.


9. Common Problems

SymptomCause
Deploy succeeds, service unhealthyApp not listening on process.env.PORT
Service unreachableBound to 127.0.0.1 instead of 0.0.0.0
Free service slow on first requestSpun down after inactivity — 30–60s cold start
Requests dropped on every deployNo SIGTERM handler
Health check restart loopHealth endpoint checks the database
too many connectionsPool size × instance count exceeds the plan limit
Build works locally, fails on RenderCase-sensitive imports, or a build dependency in devDependencies
Cron job runs but does nothingCron containers exit when the command exits — the process must complete, not detach

The Port Binding One

js
// ❌ Render cannot reach this
app.listen(3000, "127.0.0.1");

// ✅
app.listen(process.env.PORT || 3000, "0.0.0.0");

Render assigns the port via PORT and routes to the container's external interface. Hardcoding the port or binding to localhost is the single most common first-deploy failure on any container platform — Render, Railway, Fly, Heroku, Cloud Run. Worth knowing for all of them.

© 2025 DDocs · Dipak's Documentation Guide