Render Tutorials
Postgres on Render: a deep dive

Wiring services with fromDatabase

⏱ 8 min

The Blueprint primitive that connects a service to a database is fromDatabase. It pulls the connection details from the database resource at sync time and exposes them as env vars on the consuming service.

Done right, you never paste a connection string into your code or the Render Dashboard. Done badly, you end up with stale URLs, broken syncs, or worse - a DATABASE_URL in version control.

The basic pattern

render.yaml
databases:
- name: app-db
plan: basic-256mb
services:
- type: web
name: api
runtime: node
plan: starter
buildCommand: npm ci && npm run build
startCommand: npm start
envVars:
- key: DATABASE_URL
fromDatabase:
name: app-db
property: connectionString

Three things happen at sync time:

  1. Render finds the database named app-db (declared in the same Blueprint or already provisioned in the workspace).
  2. It resolves property: connectionString to the internal URL - postgres://user:password@hostname/dbname.
  3. It sets DATABASE_URL on the api service to that value.

The service never sees the URL in YAML. The URL never lives in Git. Rotating credentials (e.g. through a database migration to a new instance) is a one-PR affair.

All the properties you can pull

render.yaml - all six properties
envVars:
- key: DATABASE_URL
fromDatabase:
name: app-db
property: connectionString
- key: PGHOST
fromDatabase:
name: app-db
property: host
- key: PGPORT
fromDatabase:
name: app-db
property: port
- key: PGUSER
fromDatabase:
name: app-db
property: user
- key: PGPASSWORD
fromDatabase:
name: app-db
property: password
- key: PGDATABASE
fromDatabase:
name: app-db
property: database
PropertyValue
connectionStringFull postgres://user:password@host:port/database URL
hostInternal hostname only
portDatabase port (typically 5432)
userPostgres role
passwordPostgres password
databaseThe actual database name (the path component)

connectionString is what you’ll use 90% of the time. The discrete properties are useful when:

  • Your driver wants PGHOST, PGUSER, etc. as separate env vars (the libpq convention - psql reads them automatically).
  • You’re constructing a non-Postgres-standard connection string (some Java drivers want a jdbc: prefix; some Go drivers want key=value pairs).
  • You want to log the host or user in a startup banner without logging the password.

Multi-service consumers: web, worker, cron

A typical app has more than one service that talks to the database. Wire each one independently - declare a fromDatabase block for every consumer.

render.yaml
databases:
- name: app-db
plan: basic-256mb
services:
- type: web
name: api
runtime: node
plan: starter
buildCommand: npm ci && npm run build
startCommand: npm start
envVars:
- key: DATABASE_URL
fromDatabase:
name: app-db
property: connectionString
- type: worker
name: jobs
runtime: node
plan: starter
buildCommand: npm ci
startCommand: node worker.js
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 cleanup.js
envVars:
- key: DATABASE_URL
fromDatabase:
name: app-db
property: connectionString

Three services, one database, three independent fromDatabase blocks. Each service gets its own copy of DATABASE_URL resolved at sync time.

Splitting into a shared env group (advanced)

When you have many services pulling the same vars, an envVarGroup reduces the total YAML. The group can hold the fromDatabase block, and each service references the group:

render.yaml
envVarGroups:
- name: db-config
envVars:
- key: DATABASE_URL
fromDatabase:
name: app-db
property: connectionString
services:
- type: web
name: api
runtime: node
plan: starter
envVars:
- fromGroup: db-config
- type: worker
name: jobs
runtime: node
plan: starter
envVars:
- fromGroup: db-config

This is the same env group pattern from the advanced Blueprint patterns tutorial - applied here to keep the database URL declared in one place.

Anatomy of the connection string

When you get into debugging - or you want to write a startup log line that doesn’t leak secrets - it helps to know what the URL parts mean.

postgres://USER:PASSWORD@HOST:PORT/DATABASE
| | | | | |
| | | | | +-- Database name (path)
| | | | +-------- Port (default 5432)
| | | +------------- Hostname (internal or external)
| | +---------------------- Password
| +---------------------------- User (Postgres role)
+-------------------------------------- Scheme (postgres or postgresql)

postgres:// and postgresql:// are interchangeable - both standard, every driver accepts both. The trailing ?sslmode=require (only on external URLs) is a query parameter the driver hands to libpq.

Multiple logical databases on one instance

A single Postgres instance can host multiple logical databases - same host, port, and user, but different DATABASE path components in the URL. Useful when you have multiple small apps or environments and don’t want to pay for separate Postgres instances.

  1. Connect to the primary database with `psql` Use the connection string from the Render Dashboard or render psql <database-id>.
  2. Run `CREATE DATABASE new_db;` Standard SQL. The new database inherits the role and host of the primary.
  3. Build a new connection string Take the existing URL and swap the path component for new_db. Everything else stays the same.
  4. Wire it into a service Use fromDatabase for the host/user/password parts, then construct the URL in code or a startup script. Or use the discrete properties (PGHOST, PGUSER, etc.) and let your app fill in the database name.
render.yaml - second logical DB on the same instance
services:
- type: web
name: secondary-app
runtime: node
envVars:
- key: PGHOST
fromDatabase:
name: app-db
property: host
- key: PGPORT
fromDatabase:
name: app-db
property: port
- key: PGUSER
fromDatabase:
name: app-db
property: user
- key: PGPASSWORD
fromDatabase:
name: app-db
property: password
- key: PGDATABASE
value: new_db
- key: DATABASE_URL
value: postgres://$PGUSER:$PGPASSWORD@$PGHOST:$PGPORT/$PGDATABASE

This is genuinely useful for cost savings - three small apps sharing one basic-1gb instance is much cheaper than three separate basic-256mb instances. The tradeoffs:

  • Connection limits are shared. All logical databases on the instance count against the same max_connections cap (covered in step 06).
  • Backups are at the instance level. Restoring a snapshot brings back every logical database on it.
  • Resource contention is real. A heavy query in one logical database affects the others.

If those tradeoffs are uncomfortable, separate instances are the right call.

Validation: catch typos before they bite

The single most common fromDatabase mistake is a typo in the name: field - referencing a database that doesn’t exist.

render.yaml - broken
services:
- type: web
name: api
envVars:
- key: DATABASE_URL
fromDatabase:
name: app-bd # typo: should be 'app-db'
property: connectionString

The schema doesn’t catch this - app-bd is a valid string. Semantic validation does:

Terminal
render blueprints validate
# Error: fromDatabase references unknown database 'app-bd'

Run render blueprints validate before you commit any change to a fromDatabase block. The CLI is covered in the advanced Blueprint patterns introduction.

You're adding a third service (`reports`, a cron job) that needs read-only access to the same database your `api` and `jobs` services already use. What's the cleanest Blueprint pattern?

What you learned

  • `fromDatabase` resolves the database's internal URL (or discrete fields) at sync time and injects them as env vars on the consuming service
  • Use `connectionString` for most apps; pull discrete properties (`host`, `port`, `user`, `password`, `database`) when your driver wants them split
  • Wire each consumer with its own `fromDatabase` block - don't try to DRY with YAML anchors. An `envVarGroup` is the right tool for shared wiring
  • A single Postgres instance can hold multiple logical databases (`CREATE DATABASE`); they share host, credentials, connection caps, and backups
  • `render blueprints validate` catches typos in the `name:` reference before they break a sync