From 22e2370b359c6e8f3b01a82a8e45e1d50b86f191 Mon Sep 17 00:00:00 2001
From: "Andrey A." <56412611+aantti@users.noreply.github.com>
Date: Fri, 21 Aug 2026 15:10:18 +0200
Subject: [PATCH] docs(self-hosted): add poolers how-to guide (#49303)
---
.../NavigationMenu.constants.ts | 1 +
.../guides/api/rest/generating-types.mdx | 2 +-
.../self-hosting/accessing-postgres.mdx | 128 ++++++++++++++++++
.../content/guides/self-hosting/docker.mdx | 59 +-------
.../self-hosting/restore-from-platform.mdx | 4 +-
.../steps/direct-connection/content.tsx | 2 +-
docker/.env.example | 4 +-
7 files changed, 136 insertions(+), 64 deletions(-)
create mode 100644 apps/docs/content/guides/self-hosting/accessing-postgres.mdx
diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts
index 5498aca968b..f71fa59142a 100644
--- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts
+++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts
@@ -3092,6 +3092,7 @@ export const self_hosting: NavMenuConstant = {
items: [
{ name: 'Overview', url: '/guides/self-hosting' },
{ name: 'Deploy with Docker', url: '/guides/self-hosting/docker' },
+ { name: 'Accessing Postgres', url: '/guides/self-hosting/accessing-postgres' },
{ name: 'Configure new API keys', url: '/guides/self-hosting/self-hosted-auth-keys' },
{ name: 'Learn about API Gateway', url: '/guides/self-hosting/self-hosted-envoy' },
{
diff --git a/apps/docs/content/guides/api/rest/generating-types.mdx b/apps/docs/content/guides/api/rest/generating-types.mdx
index c728c698795..64762769bc4 100644
--- a/apps/docs/content/guides/api/rest/generating-types.mdx
+++ b/apps/docs/content/guides/api/rest/generating-types.mdx
@@ -46,7 +46,7 @@ or in case of local development:
npx supabase gen types typescript --local > database.types.ts
```
-or in case of a self-hosted instance (see [Accessing Postgres](/docs/guides/self-hosting/docker#accessing-postgres) for more information):
+or in case of a self-hosted instance (see [Accessing Postgres](/docs/guides/self-hosting/accessing-postgres#connect-through-supavisor) for more information):
```bash
npx supabase gen types typescript --db-url postgres://postgres.[POOLER_TENANT_ID]:[POSTGRES_PASSWORD]@[your-domain-or-ip]:5432/postgres --schema public > database.types.ts
diff --git a/apps/docs/content/guides/self-hosting/accessing-postgres.mdx b/apps/docs/content/guides/self-hosting/accessing-postgres.mdx
new file mode 100644
index 00000000000..c8113d9ce57
--- /dev/null
+++ b/apps/docs/content/guides/self-hosting/accessing-postgres.mdx
@@ -0,0 +1,128 @@
+---
+title: 'Accessing Postgres'
+description: 'Connect to your self-hosted Postgres database through the Supavisor or PgBouncer pooler, or with a direct connection.'
+subtitle: 'Connect to your self-hosted Postgres database through the Supavisor or PgBouncer pooler, or with a direct connection.'
+---
+
+This guide explains how to connect to Postgres in self-hosted Supabase, using the Supavisor pooler, the optional PgBouncer pooler, or a direct connection.
+
+Self-hosted Supabase uses [Supavisor](https://github.com/supabase/supavisor) as its default connection pooler. A pooler sits in front of Postgres and shares a small set of database connections across many clients, which avoids exhausting Postgres connection limits.
+
+## Choose a connection mode
+
+Self-hosted Supabase offers three ways to reach Postgres:
+
+- **Session mode** - Supavisor on port `5432`. Best for persistent clients that need per-session features such as `SET` statements, prepared statements, `LISTEN/NOTIFY`, or advisory locks. Each client holds a dedicated Postgres connection for the life of the session. Available by default.
+- **Transaction mode** - Supavisor or PgBouncer on port `6543`. Best for serverless or edge functions that open many short-lived connections. Does not support session-level features (`SET`, `LISTEN/NOTIFY`, temporary tables that span transactions, or advisory locks). Supavisor pooler does not support prepared statements; PgBouncer can be [configured to support them](#use-pgbouncer-instead-of-supavisor). Available by default.
+- **Direct connection** - Postgres bypassing the pooler. Not exposed by default - refer to [exposing Postgres](#expose-postgres-for-direct-connections). Best for migrations, `pg_dump`, and long-lived backends.
+
+## Connect through Supavisor [#connect-through-supavisor]
+
+Use your domain name, your server IP, or `localhost`, depending on where the stack runs.
+
+For session-mode connections:
+
+```sh
+psql 'postgres://postgres.[POOLER_TENANT_ID]:[POSTGRES_PASSWORD]@[your-domain]:5432/postgres'
+```
+
+For transaction-mode connections:
+
+```sh
+psql 'postgres://postgres.[POOLER_TENANT_ID]:[POSTGRES_PASSWORD]@[your-domain]:6543/postgres'
+```
+
+Supavisor requires the "tenant ID" (`your-tenant-id`) for authentication, not only the role. When using `psql` with command-line parameters instead of a connection string, the `-U` parameter must also be `postgres.[POOLER_TENANT_ID]`.
+
+## Customize Supavisor
+
+Configure Supavisor settings through your `.env` file, then recreate the stack for changes to take effect:
+
+| Variable | Default | Description |
+| ------------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
+| `POSTGRES_PORT` | `5432` | Host port for session-mode connections. |
+| `POOLER_PROXY_PORT_TRANSACTION` | `6543` | Host port for transaction-mode connections. |
+| `POOLER_DEFAULT_POOL_SIZE` | `20` | Postgres connections the pooler opens per pool. Keep this below your Postgres `max_connections` minus connections reserved for other services. |
+| `POOLER_MAX_CLIENT_CONN` | `100` | Client connections the pooler accepts. |
+| `POOLER_TENANT_ID` | `your-tenant-id` | Supavisor tenant identifier, used in the username. |
+| `POOLER_DB_POOL_SIZE` | `5` | Internal metadata pool used by Supavisor itself. |
+
+To check your current Postgres `max_connections` setting:
+
+```sh
+docker compose exec db psql -U postgres -c "SHOW max_connections;"
+```
+
+To change `max_connections` or other Postgres settings, refer to [custom Postgres configuration](/docs/guides/self-hosting/postgres-upgrade-17#custom-postgres-configuration).
+
+For the full list of Supavisor's configurable environment variables, check the reference list in [docker/CONFIG.md](https://github.com/supabase/supabase/blob/master/docker/CONFIG.md#supavisor).
+
+## Use PgBouncer instead of Supavisor
+
+Self-hosted Supabase includes an optional [PgBouncer](https://www.pgbouncer.org/) override. It disables Supavisor and runs PgBouncer in transaction mode on `POOLER_PROXY_PORT_TRANSACTION`.
+
+Add it to your stack with `run.sh`:
+
+```sh
+sh run.sh config add pgbouncer
+sh run.sh start
+```
+
+If you prefer to run Docker Compose commands explicitly, use `docker compose -f docker-compose.yml -f docker-compose.pgbouncer.yml up -d`.
+
+To connect as `postgres`:
+
+```sh
+# tenant ID isn't required for PgBouncer
+psql 'postgres://postgres:[POSTGRES_PASSWORD]@[your-domain]:6543/postgres'
+```
+
+The PgBouncer override provides transaction mode only. For session-mode connections, or for features that transaction mode does not support (such as `SET` statements or `LISTEN/NOTIFY`), reconfigure PgBouncer manually by editing its environment variables in `docker-compose.pgbouncer.yml`, or use a [direct connection](#expose-postgres-for-direct-connections). PgBouncer reuses the `POOLER_DEFAULT_POOL_SIZE` and `POOLER_MAX_CLIENT_CONN` values from your `.env` configuration.
+
+## Expose Postgres for direct connections
+
+In the default configuration, Postgres is only reachable through the pooler. To bypass the pooler for migrations, `pg_dump`, or other direct-connection needs, expose the Postgres port.
+
+
Manually{' '}