Render Tutorials
Render CLI for power users

SSH, ephemeral shells, and psql

⏱ 11 min

When logs aren’t enough - you need to actually look at the filesystem, hit the database, or run a one-off command in the service’s environment - the CLI gives you two surfaces: render ssh for live instances, and render psql for the database. Both work great interactively and even better in scripts.

render ssh: a shell on a running instance

Terminal
SRV=$(render-id api)
render ssh "$SRV"

You land in a shell on the actual running instance. Same filesystem, same env vars, same PATH. Anything you do affects production traffic - exit when you’re done.

Run a single command and exit

For scripts, pass the command as the final argument(s):

Terminal
render ssh "$SRV" -- env | grep DATABASE_URL
Terminal
render ssh "$SRV" -- ls -la /tmp

The -- separator tells the CLI “anything after this is the remote command.” It’s exactly like ssh user@host -- command.

Quick recipes

Terminal
# What env vars does the running service actually see?
render ssh "$SRV" -- env
# What version of Node/Python/Ruby is actually running?
render ssh "$SRV" -- node --version
# Disk usage - helpful when a persistent disk is filling up
render ssh "$SRV" -- df -h
# Heap snapshot for Node (writes to the disk that gets discarded on next deploy)
render ssh "$SRV" -- node -e 'require("v8").writeHeapSnapshot("/tmp/heap.heapsnapshot")'

The last one is in the “carefully” category - you’re running a real command in the live process’s environment. Fine for inspection, off-limits for cleanup-style mutations.

render ssh --ephemeral: the safe playground

--ephemeral (or -e) launches a fresh container, isolated from production traffic, with the service’s image and env vars but no incoming connections. Spin one up, mess around, exit, and it’s gone.

Terminal
render ssh "$SRV" -e

This is the right surface for:

Use it forWhy
Running a one-off database migrationProduction traffic isn’t affected if it goes wrong; you exit and nothing persists
Reproducing a bug with a debugger attachedYou can take your time without holding up real users
Testing a new dependency or binary versionInstall, test, exit - the image isn’t permanently changed
Running an rspec / pytest / smoke check against prod env varsSame environment as the real service, but you can break it freely

The shell exits and the container is destroyed. No cleanup, no lingering state.

Pre-deploy migration check pattern

A real-world example - run pending migrations in an isolated container before flipping a deploy:

Terminal
render ssh "$SRV" -e -- npm run migrate:status

If the output is what you expected, run the real migration in CI as part of a build step. If it isn’t, you’ve caught the surprise before production saw it.

render psql: one-shot SQL

render psql opens a psql session against a Render Postgres database - same auth, internal/external routing handled, no copy-pasted connection strings. Interactive use is exactly like psql:

Terminal
DB=$(render-id app-db)
render psql "$DB"

You’re in the psql REPL. \q to exit.

One-shot queries

The power-user form takes -c for a single query, with -o json (or -o text -- --csv) for parseable output:

Terminal
render psql "$DB" -c "SELECT NOW();" -o json
Terminal
render psql "$DB" -c "SELECT count(*) FROM users WHERE created_at > NOW() - INTERVAL '24 hours';" -o json

The -o json flag wraps the result as a JSON array of row objects - directly pipeable to jq.

CSV output

For exports, hand flags through to the underlying psql with the -- separator:

Terminal
render psql "$DB" -c "SELECT id, email, created_at FROM users LIMIT 100;" -o text -- --csv \
> users-sample.csv

-- --csv says “after this, pass the rest as flags to psql itself.” psql’s --csv mode emits proper RFC 4180 CSV.

Run a file

For multi-statement scripts:

Terminal
render psql "$DB" -- -f migrations/2026-05-21-add-index.sql

-- -f file.sql is identical to psql -f file.sql - the -- lets you pass any psql flag through.

Scripted data checks

Two patterns you’ll reach for constantly.

Daily count snapshot

daily-snapshot.sh
#!/usr/bin/env bash
set -euo pipefail
export RENDER_API_KEY="${RENDER_API_KEY:?must be set}"
export RENDER_OUTPUT=json
DB="${RENDER_DB_ID:?must be set}"
render psql "$DB" -c "
SELECT
(SELECT count(*) FROM users) AS users_total,
(SELECT count(*) FROM users WHERE created_at::date = CURRENT_DATE) AS users_today,
(SELECT count(*) FROM orders WHERE created_at::date = CURRENT_DATE) AS orders_today;
" | jq '.[0]'

Pipe the result into Slack, a webhook, your inbox - whatever your team reads.

Pre-migration row-count check

check-migration-safe.sh
#!/usr/bin/env bash
set -euo pipefail
export RENDER_API_KEY="${RENDER_API_KEY:?must be set}"
export RENDER_OUTPUT=json
DB="${RENDER_DB_ID:?must be set}"
threshold=1000000
n=$(render psql "$DB" -c "SELECT count(*) FROM events;" | jq -r '.[0].count')
if (( n > threshold )); then
echo "events has $n rows (> $threshold). Migration may be slow; consider running off-hours." >&2
exit 1
fi
echo "events has $n rows. Safe to migrate."

render psql -c "SELECT count(*)..." -o json returns [{ "count": 1234 }]. The jq -r '.[0].count' extracts just the number.

Combining ssh and psql

Sometimes you want a SQL session as the service itself - same DATABASE_URL, same network position:

Terminal
render ssh "$SRV" -e -- psql "$DATABASE_URL"

This is the right choice for:

  • Running a query that depends on a Postgres extension only installed inside the container
  • Reproducing connection pool behavior from the service’s perspective
  • Testing IP allowlists by going through the service’s network path

The -e (ephemeral) makes it safe to experiment. Exit when you’re done.

When to use which

flowchart TD
  q["What do you want?"]
  l["Read service logs"]
  inspect["Inspect live state (env, files, processes)"]
  experiment["Run a risky command"]
  query["Run a SQL query"]
  scriptq["Run a SQL query from a script"]

  logs["render logs"]
  ssh["render ssh"]
  e["render ssh --ephemeral"]
  psql["render psql"]
  scripted["render psql -c... -o json"]

  q --> l --> logs
  q --> inspect --> ssh
  q --> experiment --> e
  q --> query --> psql
  q --> scriptq --> scripted

Pick the smallest tool that answers the question. The cost of using render logs is nothing; the cost of using render ssh is “be careful.” The cost of --ephemeral is “a few extra seconds to spin up a container.” The cost of psql in the middle of a busy database is exactly what you’d expect.

Common mistakes

MistakeFix
Running destructive commands in render ssh (no --ephemeral)Use --ephemeral for anything riskier than ls and env
Forgetting -c and getting an interactive psql in a scriptAlways pass -c "SQL" for scripts; reserve interactive psql for humans
Parsing psql text output with grepUse -o json and jq, or -o text -- --csv for CSV
render ssh hanging in CISSH isn’t for CI - use render deploys create and pre-deploy hooks instead
You need to run a long-lived data migration script that you've never run on this database before. Which surface is safest?

What you learned

  • `render ssh SRV` puts you in the live container; use `-- command` to run one thing and exit
  • `render ssh SRV --ephemeral` is the safe playground - fresh container, same env, gone when you exit
  • `render psql DB -c "SQL" -o json` is the scripting form; pipe to `jq` for whatever you need
  • `-- --csv` and `-- -f file.sql` pass flags through to the underlying `psql`
  • Pick the smallest tool that answers the question - logs before ssh, ssh -e before ssh, scripted psql before interactive