---
id: platform/postgres
title: Postgres
description: Create managed PostgreSQL, connect with returned credentials, and plan storage, backups, recovery, and upgrades.
keywords: [bex, postgres, postgresql, managed database, connection string]
last_updated: 2026-09-23
---

Bex manages PostgreSQL through CloudNativePG. A database is a separate resource
from your application services: several applications can connect to it, and
replacing an application does not replace the database.

Compute, storage, backup support, and high availability depend on the selected
plan and configured infrastructure. A created resource is not yet a ready
database, and configured backups are not proof of a completed recovery point.

## Create a database

Use the dashboard's Postgres creation flow, or declare a database in a
[`render.yaml` Blueprint](./app-resource.md):

```yaml
databases:
  - name: app-db
    plan: free
    postgresMajorVersion: "16"
```

Choose a supported plan and version for your application. The Blueprint field
is **`postgresMajorVersion`**; the REST create field is `version`. Do not copy
field names between these formats.

To create an explicitly private database through REST, set `BEX_API_URL` to
your instance's API origin and `BEX_TOKEN` to an authorized workspace token:

```bash
curl --fail-with-body -X POST "$BEX_API_URL/v1/postgres" \
  -H "Authorization: Bearer $BEX_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"app-db","plan":"free","version":"16","public":false}'
```

Check the target workspace and resource plan before creating it. REST defaults
`public` to true when omitted for Render-client compatibility; the shared
core and Blueprint workflow default differently. Set access intentionally,
and inspect the resulting resource rather than assuming every interface has
the same default.

The resource id is stable. Its display name can change; PostgreSQL's database
name and database user are separate identifiers chosen at creation and are
immutable. Wait for the database to become available before connecting.

## Retrieve connection details

Open the database's Connections panel, or set `DATABASE_ID` to its returned id:

```bash
curl --fail-with-body "$BEX_API_URL/v1/postgres/$DATABASE_ID/connection-info" \
  -H "Authorization: Bearer $BEX_TOKEN"
```

This response contains credentials and requires permission to view sensitive
values. Keep it out of source control, public logs, and shared transcripts.

| Connection | Use it for |
| --- | --- |
| `internalConnectionString` | Applications on an allowed Bex private-network path. |
| `externalConnectionString` | External clients when public access and the instance's database gateway are configured. |
| `internalConnectionPoolString` / `externalConnectionPoolString` | PgBouncer connections when the pooler and corresponding route are ready. |

Do not assemble a hostname from the display name, assume a `.default.svc`
namespace, or reuse another database's URL. External hostnames depend on the
instance's configured database domain. Public access enabled without a working
external host can cause connection-info to report that infrastructure is
unavailable.

For an external connection, preserve the returned `sslmode=verify-full` and
configure your PostgreSQL client's trust store for the issuing CA. Do not
replace the hostname with an IP or weaken verification to resolve a certificate
error. Internal URLs do not themselves promise TLS; follow the instance's
internal transport policy rather than assuming private networking is encryption.

After securely setting `DATABASE_URL` to the appropriate returned URL, test a
read-only query:

```bash
psql "$DATABASE_URL" -c 'SELECT 1;'
```

## Connect an application with a Blueprint reference

Let Bex supply the credential through an environment reference:

```yaml
services:
  - name: api
    type: web
    runtime: docker
    repo: https://github.com/your-org/api
    envVars:
      - key: DATABASE_URL
        fromDatabase:
          name: app-db
          property: connectionString
databases:
  - name: app-db
    plan: free
    postgresMajorVersion: "16"
```

Replace the repository and validate the complete Blueprint. Your application
reads `DATABASE_URL`; the password does not belong in the checked-in YAML.
See [Secrets](./secrets.md) and [Private network](./private-network.md).

## Restrict access and manage capacity

External IP allowlists constrain the public gateway. Internal connections still
require valid credentials and an allowed workspace/environment network path;
they are not universally reachable from every Bex workload.

Storage grows but does not shrink. Plan changes and version changes can affect
availability; check requested and actual capacity after reconciliation.
Storage autoscaling is a separate database setting, not application replica
scaling. Monitor storage usage, connections, and query behavior before choosing
more compute or storage.

PgBouncer can reduce connection pressure when configured. Rehearse direct versus
pooled clients before scaling with
[Postgres pooling before scaling](./postgres-pooling.md). Read replicas can
serve read-only workloads; their connection details are separate from the
primary's. High availability provides a standby/failover configuration where
supported. Neither a standby nor a replica replaces a backup against accidental
deletes or bad migrations.

## Back up and recover

Check the database's Recovery view before relying on point-in-time recovery.
Through REST, the current recovery-info operation uses POST:

```bash
curl --fail-with-body -X POST \
  "$BEX_API_URL/v1/postgres/$DATABASE_ID/recovery-info" \
  -H "Authorization: Bearer $BEX_TOKEN"
```

A backup-capable plan still needs configured backup storage, completed backups,
and an established recovery window. Free databases do not provide the same
recovery capability. If no usable window is reported, do not assume a selected
time can be restored.

Recovery creates a **new database** from the source backup; it does not rewind
the source in place. Choose a new name and a target time within the reported
window. The API accepts these as `name` and `targetTime` at
`POST /v1/postgres/{id}/recover`. Wait for recovery to finish, inspect the data,
then deliberately update application connections. A successful request is not
proof that recovery has completed.

Logical exports use `POST /v1/postgres/{id}/export` to request an export and
`GET /v1/postgres/{id}/export` to inspect available results. Protect downloaded
data and export URLs. Test restoration before depending on an export for a
migration or disaster-recovery plan.

Self-hosted operators rehearsing **platform/auth** Postgres archives (not a
tenant database recovered through this product API) should use the isolated
drill in [Recovery: Postgres](./recovery-postgres.md) and the shared
[Platform recovery](./recovery.md) preparation guide.

## Upgrade or remove a database

Major-version upgrades use an offline upgrade path, so schedule an interruption,
check extension/application compatibility, and verify a completed backup first.
Bex gates upgrades on backup readiness for plans that require it. Do not treat
an enabled-backup flag as a substitute for a completed backup.

Suspend and resume preserve the resource configuration; restart interrupts
connections. Deletion removes the managed database and its storage. Export
anything you need to retain before deletion, and check the exact resource id.
See [App lifecycle](./app-lifecycle.md) for the related operational concepts.

## Troubleshoot

For failed connections, check readiness, the chosen internal/external path,
network policy, credentials, and TLS trust. If connections exhaust the limit,
inspect pool sizes across all application replicas. For recovery failures,
inspect the available recovery window and backup status before retrying.
Persistent application files belong on [service disks](./persistent-disks.md),
which have a different backup and recovery model.
