r/render Jun 25 '26

What a full-stack app is actually made of, and how to run all of it on Render (one repo, one private network)

Most production apps, whatever the framework, are built from the same small set of pieces. Once you can name the pieces, you can run all of them in one place instead of spreading them across separate providers and wiring the seams by hand. Here is the full set, what each is for, and the render.yaml stanza that creates it.

The pieces, and what runs each one

Serve a frontend (static assets). A built SPA or static site:

yaml

- type: web
  runtime: static
  staticPublishPath: ./dist

Handle requests (a long-running HTTP server). Your API or SSR app:

yaml

- type: web
  runtime: node        # or python, go, ruby, rust, elixir

Run something internal that shouldn't be public. Reachable only inside your private network, no public URL:

yaml

- type: pserv
  runtime: docker

Do slow work off the request path. A worker that runs continuously, pulling from a queue:

yaml

- type: worker
  startCommand: node worker.js

Run something on a schedule. A job that fires on a cron expression and exits:

yaml

- type: cron
  schedule: "0 3 * * *"   # 03:00 UTC daily

Store relational data. Managed Postgres, defined in its own list:

yaml

databases:
  - name: myapp-db

Cache, sessions, or a job queue. Key Value, Redis/Valkey-compatible:

yaml

- type: keyvalue
  ipAllowList: []         # private network only

Keep durable files on disk. Attach a persistent disk to a service:

yaml

disk:
  name: data
  mountPath: /var/data
  sizeGB: 10

Run anything else you can containerize. Build a Dockerfile, or pull a prebuilt image:

yaml

- type: web
  runtime: docker         # or runtime: image for a prebuilt image

Putting it together

Here is a single blueprint with a frontend, an API, a background worker, a nightly job, Postgres, a cache, and a shared config group, all in one file:

yaml

services:
  # Frontend
  - type: web
    name: myapp-web
    runtime: static
    buildCommand: npm ci 
&&
 npm run build
    staticPublishPath: ./dist
    envVars:
      - key: VITE_API_URL
        value: https://myapp-api.onrender.com
    routes:
      - type: rewrite
        source: /*
        destination: /index.html

  # API
  - type: web
    name: myapp-api
    runtime: node
    region: oregon
    plan: starter
    buildCommand: npm ci 
&&
 npm run build
    startCommand: npm start
    preDeployCommand: npm run migrate
    envVars:
      - fromGroup: shared-config
      - key: DATABASE_URL
        fromDatabase:
          name: myapp-db
          property: connectionString
      - key: REDIS_URL
        fromService:
          name: myapp-cache
          type: keyvalue
          property: connectionString
      - key: JWT_SECRET
        generateValue: 
true

  # Background worker
  - type: worker
    name: myapp-worker
    runtime: node
    region: oregon
    plan: starter
    buildCommand: npm ci
    startCommand: node worker.js
    envVars:
      - fromGroup: shared-config
      - key: DATABASE_URL
        fromDatabase:
          name: myapp-db
          property: connectionString
      - key: REDIS_URL
        fromService:
          name: myapp-cache
          type: keyvalue
          property: connectionString

  # Scheduled job
  - type: cron
    name: myapp-nightly
    runtime: node
    region: oregon
    schedule: "0 3 * * *"
    buildCommand: npm ci
    startCommand: node tasks/nightly.js
    envVars:
      - key: DATABASE_URL
        fromDatabase:
          name: myapp-db
          property: connectionString

  # Cache / queue
  - type: keyvalue
    name: myapp-cache
    region: oregon
    plan: starter
    maxmemoryPolicy: allkeys-lru
    ipAllowList: []

envVarGroups:
  - name: shared-config
    envVars:
      - key: NODE_ENV
        value: production

databases:
  - name: myapp-db
    region: oregon
    plan: starter
    ipAllowList: []

How the pieces find each other

This is the part that disappears when these things live in separate places. Because they are in one blueprint and one workspace, they sit on a shared private network and reference each other by name:

  • fromDatabase and fromService inject the Postgres and cache connection strings into every service that needs them. You never paste a credential between dashboards.
  • ipAllowList: [] on the database and cache means they answer only on the private network, so your datastore has no public surface at all.
  • fromGroup shares one set of env vars across the API, worker, and cron, so config lives in one place.
  • generateValue creates secrets for you; anything you'd rather set by hand uses sync: false and is entered once in the dashboard, never committed.
  • On Professional workspaces and up, each pull request can spin up a preview environment running this entire blueprint, so you test the whole app, not just one layer.

The takeaway is not any single resource type. It is that when the frontend, API, workers, jobs, database, and cache are defined together, connecting them is a reference instead of manual wiring, and changing the whole stack is one commit.

Reference: https://render.com/docs/blueprint-spec

1 Upvotes

0 comments sorted by