For AI assistants (Claude, Codex, Cursor, OpenCode, etc.)
ALWAYS readllms.txtfor curated documentation pages and examples.
Connecting to Postgres
# Connecting to Postgres
In the future, Zero will work with many different backend databases. Today only Postgres is supported. Specifically, Zero requires Postgres v15.0 or higher, and support for [logical replication](https://www.postgresql.org/docs/current/logical-replication.html).
Here are some common Postgres options and what we know about their support level:
| Postgres | Support Status |
| ----------------------------------------------- | ----------------------------------------------------------------------------------------- |
| AWS RDS | ✅ |
| AWS Aurora | ✅ v15.6+ |
| Google Cloud SQL | ✅ See [notes below](#google-cloud-sql) |
| [Fly.io](https://fly.io) Managed Postgres (MPG) | ⚠️ Private network only; no [event triggers](#event-triggers); see [notes below](#flyio) |
| Neon | ✅ See [notes below](#neon) |
| PlanetScale for Postgres | ✅ See [notes below](#planetscale-for-postgres) |
| Postgres.app | ✅ |
| postgres:16.2-alpine docker image | ✅ |
| Supabase | ⚠️ See [notes below](#supabase) |
| Render | ⚠️ See [notes below](#render) |
| Heroku | 🤷♂️ No [event triggers](#event-triggers) |
## Common Problems
* `too many connections` / `remaining connection slots are reserved`: use pooled URLs for `ZERO_CVR_DB` and `ZERO_CHANGE_DB`, and tune `ZERO_CVR_MAX_CONNS`, `ZERO_CHANGE_MAX_CONNS`, and `ZERO_UPSTREAM_MAX_CONNS` (see [zero-cache Config](https://zero.rocicorp.dev/docs/zero-cache-config)).
* Provider docs for pooling/proxies: [Neon](https://neon.tech/docs/connect/connection-pooling), [PlanetScale](https://planetscale.com/docs/postgres/connecting/pgbouncer), [Supabase](https://supabase.com/docs/guides/database/connecting-to-postgres), [AWS RDS Proxy](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/rds-proxy.html), [Fly MPG](https://fly.io/docs/mpg/create-and-connect/), [Render](https://render.com/docs/postgresql), [Cloud SQL](https://docs.cloud.google.com/sql/docs/postgres/managed-connection-pooling).
* `prepared statement ... does not exist`: your pooler is likely in transaction mode; use a session pooler (or a pooler that supports prepared statements) for `ZERO_CVR_DB` and `ZERO_CHANGE_DB`.
* `permission denied to create event trigger`: your provider/role doesn’t grant superuser; Zero will fall back to full resets on schema changes.
* `permission denied` creating a publication for a schema/all tables: create a publication listing tables explicitly (see [Limiting Replication](https://zero.rocicorp.dev/docs/postgres-support#limiting-replication)) and set [App Publications](https://zero.rocicorp.dev/docs/zero-cache-config#app-publications).
## Event Triggers
Zero uses Postgres “ [Event Triggers](https://www.postgresql.org/docs/current/sql-createeventtrigger.html)” when possible to implement high-quality, efficient [schema migration](https://zero.rocicorp.dev/docs/schema#migrations).
Some hosted Postgres providers don’t provide access to Event Triggers.
Some managed providers also have incomplete Event Trigger behavior for certain DDL (for example, `ALTER PUBLICATION`). We call out known provider-specific issues below.
Zero still works out of the box with these providers, but for correctness, any schema change triggers a full reset of all server-side and client-side state. For small databases (\< 10GB) this can be OK, but for bigger databases we recommend choosing a provider that grants access to Event Triggers.
## Configuration
### WAL Level
The Postgres `wal_level` config parameter has to be set to `logical`. You can check what level your pg has with this command:
```bash
psql -c 'SHOW wal_level'
```
If it doesn’t output `logical` then you need to change the wal level. To do this, run:
```bash
psql -c "ALTER SYSTEM SET wal_level = 'logical';"
```
Then restart Postgres. On most pg systems you can do this like so:
```bash
data_dir=$(psql -t -A -c 'SHOW data_directory')
pg_ctl -D "$data_dir" restart
```
After your server restarts, show the `wal_level` again to ensure it has changed:
```bash
psql -c 'SHOW wal_level'
```
### Bounding WAL Size
For development databases, you can set a `max_slot_wal_keep_size` value in Postgres. This will help limit the amount of WAL kept around.
This is a configuration parameter that bounds the amount of WAL kept around for replication slots, and [invalidates the slots that are too far behind](https://www.postgresql.org/docs/current/runtime-config-replication.html#GUC-MAX-SLOT-WAL-KEEP-SIZE).
`zero-cache` will automatically detect if the replication slot has been invalidated and re-sync replicas from scratch.
This configuration can cause problems like `slot has been invalidated because it exceeded the maximum reserved size` and is not recommended for production databases.
## Provider-Specific Notes
### Google Cloud SQL
Zero works with Google Cloud SQL out of the box. In many configurations, when you connect with a user that has sufficient privileges, `zero-cache` will create its default publication automatically.
If your Cloud SQL user does not have permission to create publications, you can still use Zero by [creating a publication manually](https://zero.rocicorp.dev/docs/postgres-support#limiting-replication) and then specifying that publication name in [App Publications](https://zero.rocicorp.dev/docs/zero-cache-config#app-publications) when running `zero-cache`.
On Google Cloud SQL for PostgreSQL, enable logical decoding by turning on the instance flag `cloudsql.logical_decoding`. You do not set `wal_level` directly on Cloud SQL. See Google's documentation for details: [Configure logical replication](https://cloud.google.com/sql/docs/postgres/replication/configure-logical-replication).
### Fly.io
Fly's legacy "Fly Postgres" offering is deprecated; the current product is Fly Managed Postgres (MPG).
Fly Managed Postgres is private-network-only by default. If `zero-cache` runs outside Fly, connect via Fly WireGuard or run a proxy like [fly-mpg-proxy](https://github.com/fly-apps/fly-mpg-proxy) (restrict ingress / IP allowlist).
If you use Fly's pgBouncer endpoint for pooled connections (for example, for `ZERO_CVR_DB` / `ZERO_CHANGE_DB`), note that it defaults to **session** pooling; keep it that way. Transaction pooling can break prepared statements.
Fly Managed Postgres does not provide superuser access, so `zero-cache` cannot create [event triggers](#event-triggers).
Also, some publication operations (like `FOR TABLES IN SCHEMA ...` / `FOR ALL TABLES`) can be permission-restricted. If `zero-cache` can’t create its default publication, create one listing tables explicitly and set [App Publications](https://zero.rocicorp.dev/docs/zero-cache-config#app-publications).
Fly does not support TLS on its private network. If `zero-cache` connects to Postgres over the Fly private network (including WireGuard), add `sslmode=disable` to your connection strings.
### Supabase
#### Postgres Version
Supabase requires at least 15.8.1.083 for event trigger support. If you have a lower 15.x, Zero will still work but [schema updates will be slower](#event-triggers). See Supabase's docs for upgrading your Postgres version.
#### Connection Type (Direct vs Pooler)
`ZERO_UPSTREAM_DB` must use the "Direct Connection" string (not the pooler):

This is because Zero sets up a logical replication slot, which is only supported with a direct connection.
For `ZERO_CVR_DB` and `ZERO_CHANGE_DB`, prefer Supabase’s **session** pooler (not the default **transaction** pooler). The transaction pooler can break prepared statements and cause errors like `26000 prepared statement ... does not exist`.
Known limitation: Supabase does not fire `ddl_command_start`/ `ddl_command_end` event triggers for `ALTER PUBLICATION`, so Zero may not automatically detect publication changes.
#### IPv4
You may also need to assign an IPv4 address to your Supabase instance:

This will be required if you cannot use IPv6 from wherever `zero-cache` is running. Most cloud providers support IPv6, but some do not. For example, if you are running `zero-cache` in AWS, it is possible to use IPv6 but difficult. [Hetzner](https://www.hetzner.com/) offers cheap hosted VPS that supports IPv6.
IPv4 addresses are only supported on the Pro plan and are an extra $4/month.
### Render
Render can work with Zero, but commonly requires admin/support-side setup:
* Ensure `wal_level=logical` (may require a Render support ticket).
* App roles typically can’t create [event triggers](#event-triggers), so schema changes will fall back to full resets.
* App roles may not be able to create schema/all-table publications; create a publication listing tables explicitly and set [App Publications](https://zero.rocicorp.dev/docs/zero-cache-config#app-publications).
### PlanetScale for Postgres
* Use the `default` role that PlanetScale provides, because PlanetScale user-defined roles cannot create replication slots.
* Use a direct connection for `ZERO_UPSTREAM_DB` and the pgBouncer connection string for `ZERO_CVR_DB` and `ZERO_CHANGE_DB`. Otherwise you will likely exhaust connection limits on PlanetScale.
### Neon
Neon supports logical replication, but you may need to enable it in the Neon console for your branch/endpoint (otherwise `SHOW wal_level` may return `replica`).
Neon fully supports Zero, but you should be aware of how Neon's pricing model and Zero interact.
Because Zero keeps an open connection to Postgres to replicate changes, as long as zero-cache is running, Postgres will be running and you will be charged by Neon.
For production databases that have enough usage to always be running anyway, this is fine. But for smaller applications that would otherwise not always be running, this can create a surprisingly high bill. You may want to choose a provider that charge a flat monthly rate instead.
Also some users choose Neon because they hope to use branching for previews. This can work, but if not done with care, Zero can end up keeping each Neon *preview* branch running too 😳.
For the recommended approach to preview URLs, see [Preview Deployments](https://zero.rocicorp.dev/docs/preview-deployments).
Or, for AI assistants
ALWAYS readllms.txtfor curated documentation pages and examples.
In the future, Zero will work with many different backend databases. Today only Postgres is supported. Specifically, Zero requires Postgres v15.0 or higher, and support for logical replication.
Here are some common Postgres options and what we know about their support level:
too many connections / remaining connection slots are reserved: use pooled URLs for ZERO_CVR_DB and ZERO_CHANGE_DB, and tune ZERO_CVR_MAX_CONNS, ZERO_CHANGE_MAX_CONNS, and ZERO_UPSTREAM_MAX_CONNS (see zero-cache Config).
prepared statement ... does not exist: your pooler is likely in transaction mode; use a session pooler (or a pooler that supports prepared statements) for ZERO_CVR_DB and ZERO_CHANGE_DB.
permission denied to create event trigger: your provider/role doesn’t grant superuser; Zero will fall back to full resets on schema changes.
permission denied creating a publication for a schema/all tables: create a publication listing tables explicitly (see Limiting Replication) and set App Publications.
Some hosted Postgres providers don’t provide access to Event Triggers.
Some managed providers also have incomplete Event Trigger behavior for certain DDL (for example, ALTER PUBLICATION). We call out known provider-specific issues below.
Zero still works out of the box with these providers, but for correctness, any schema change triggers a full reset of all server-side and client-side state. For small databases (< 10GB) this can be OK, but for bigger databases we recommend choosing a provider that grants access to Event Triggers.
zero-cache will automatically detect if the replication slot has been invalidated and re-sync replicas from scratch.
This configuration can cause problems like slot has been invalidated because it exceeded the maximum reserved size and is not recommended for production databases.
Zero works with Google Cloud SQL out of the box. In many configurations, when you connect with a user that has sufficient privileges, zero-cache will create its default publication automatically.
If your Cloud SQL user does not have permission to create publications, you can still use Zero by creating a publication manually and then specifying that publication name in App Publications when running zero-cache.
On Google Cloud SQL for PostgreSQL, enable logical decoding by turning on the instance flag cloudsql.logical_decoding.
You do not set wal_level directly on Cloud SQL.
See Google's documentation for details: Configure logical replication.
Fly's legacy "Fly Postgres" offering is deprecated; the current product is Fly Managed Postgres (MPG).
Fly Managed Postgres is private-network-only by default. If zero-cache runs outside Fly, connect via Fly WireGuard or run a proxy like fly-mpg-proxy (restrict ingress / IP allowlist).
If you use Fly's pgBouncer endpoint for pooled connections (for example, for ZERO_CVR_DB / ZERO_CHANGE_DB), note that it defaults to session pooling; keep it that way. Transaction pooling can break prepared statements.
Fly Managed Postgres does not provide superuser access, so zero-cache cannot create event triggers.
Also, some publication operations (like FOR TABLES IN SCHEMA ... / FOR ALL TABLES) can be permission-restricted. If zero-cache can’t create its default publication, create one listing tables explicitly and set App Publications.
Fly does not support TLS on its private network. If zero-cache connects to Postgres over the Fly private network (including WireGuard), add sslmode=disable to your connection strings.
Supabase requires at least 15.8.1.083 for event trigger support. If you have a lower 15.x, Zero will still work but schema updates will be slower. See Supabase's docs for upgrading your Postgres version.
ZERO_UPSTREAM_DB must use the "Direct Connection" string (not the pooler):
Use the "Direct Connection" option for ZERO_UPSTREAM_DB.
This is because Zero sets up a logical replication slot, which is only supported with a direct connection.
For ZERO_CVR_DB and ZERO_CHANGE_DB, prefer Supabase’s session pooler (not the default transaction pooler). The transaction pooler can break prepared statements and cause errors like 26000 prepared statement ... does not exist.
Known limitation: Supabase does not fire ddl_command_start/ddl_command_end event triggers for ALTER PUBLICATION, so Zero may not automatically detect publication changes.
You may also need to assign an IPv4 address to your Supabase instance:
Assign an IPv4 address if you have trouble connecting from residential internet.
This will be required if you
cannot use IPv6 from wherever zero-cache is running. Most cloud providers
support IPv6, but some do not. For example, if you are running zero-cache in AWS, it is possible to use IPv6 but
difficult. Hetzner offers cheap hosted VPS that supports IPv6.
IPv4 addresses are only supported on the Pro plan and are an extra $4/month.
Use the default role that PlanetScale provides, because PlanetScale user-defined roles cannot create replication slots.
Use a direct connection for ZERO_UPSTREAM_DB and the pgBouncer connection string for ZERO_CVR_DB and ZERO_CHANGE_DB. Otherwise you will likely exhaust connection limits on PlanetScale.
Neon supports logical replication, but you may need to enable it in the Neon console for your branch/endpoint (otherwise SHOW wal_level may return replica).
Neon fully supports Zero, but you should be aware of how Neon's pricing model and Zero interact.
Because Zero keeps an open connection to Postgres to replicate changes, as long as zero-cache is running, Postgres will be running and you will be charged by Neon.
For production databases that have enough usage to always be running anyway, this is fine. But for smaller applications that would otherwise not always be running, this can create a surprisingly high bill. You may want to choose a provider that charge a flat monthly rate instead.
Also some users choose Neon because they hope to use branching for previews. This can work, but if not done with care, Zero can end up keeping each Neon preview branch running too 😳.