diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts
index 5498aca968b67..f71fa59142a44 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 c728c698795b4..64762769bc451 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 0000000000000..c8113d9ce57b9
--- /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.
+
+
+
+Exposing Postgres opens your database to the network. Configure firewall rules or network policies to restrict access to Postgres.
+
+
+
+If you use the default Supavisor stack, edit `docker-compose.yml`:
+
+1. Disable Supavisor by commenting out or removing the entire `supavisor` service section.
+2. Expose the Postgres port by adding the port mapping to the `db` service:
+
+```yaml name=docker-compose.yml
+db:
+ ports:
+ - ${POSTGRES_PORT}:${POSTGRES_PORT}
+ container_name: supabase-db
+```
+
+
+
+If you want to keep Supavisor running alongside a direct connection, map Postgres to a different host port (for example, `5433:${POSTGRES_PORT}`) instead of disabling Supavisor.
+
+
+
+If you use the PgBouncer override, Supavisor is already disabled. Uncomment the `db` block in `docker-compose.pgbouncer.yml` instead:
+
+```yaml name=docker-compose.pgbouncer.yml
+db:
+ ports:
+ - ${POSTGRES_PORT}:${POSTGRES_PORT}
+```
+
+After restarting, connect directly with a standard Postgres connection string:
+
+```sh
+postgres://postgres:[POSTGRES_PASSWORD]@[your-server-ip]:5432/[POSTGRES_DB]
+```
+
+## Additional resources
+
+- [Supavisor documentation](https://supabase.github.io/supavisor/development/docs/)
+- [PgBouncer documentation](https://www.pgbouncer.org/config.html)
+- [Connect to your database](/docs/guides/database/connecting-to-postgres)
diff --git a/apps/docs/content/guides/self-hosting/docker.mdx b/apps/docs/content/guides/self-hosting/docker.mdx
index dcbeddf3a5f4f..6b17aced1e484 100644
--- a/apps/docs/content/guides/self-hosting/docker.mdx
+++ b/apps/docs/content/guides/self-hosting/docker.mdx
@@ -319,27 +319,7 @@ You will be prompted for a username and password. See the [Studio authentication
## Accessing Postgres
-The self-hosted Supabase stack provides the [Supavisor](https://supabase.github.io/supavisor/development/docs/) connection pooler for accessing Postgres and managing database connections.
-
-You can connect to the Postgres database via Supavisor using the methods described below. Use your domain name, your server IP, or `localhost` depending on whether you are running self-hosted Supabase on a VPS, or locally.
-
-The default `POOLER_TENANT_ID` is `your-tenant-id` (can be later changed in `.env`), and the password is the value of `POSTGRES_PASSWORD` from the `.env` file.
-
-For session-mode connections (equivalent to a direct Postgres connection):
-
-```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'
-```
-
-When using `psql` with command-line parameters instead of a connection string to connect to Supavisor, the `-U` parameter should also be `postgres.[POOLER_TENANT_ID]`.
-
-If you need to configure Postgres to be directly accessible from the Internet, read [Exposing your Postgres database](#exposing-your-postgres-database).
+Self-hosted Supabase pools Postgres connections through Supavisor by default, with an optional PgBouncer pooler and a direct-connection option. For connection strings, pooler configuration, switching poolers, and exposing Postgres directly, see [Accessing Postgres](/docs/guides/self-hosting/accessing-postgres).
To change the database password, read [Changing database password](#changing-database-password).
@@ -590,43 +570,6 @@ By default, the Storage backend uses local files via a bind mount. On macOS, Doc
Configuring the Supabase AI Assistant is optional. By adding **your own** `OPENAI_API_KEY` to `.env` you can enable AI services, which help with writing SQL queries, statements, and policies.
-### Accessing Postgres through Supavisor
-
-By default, Postgres connections go through the Supavisor connection pooler for efficient connection management. Two ports are available:
-
-- `POSTGRES_PORT` (default: 5432) - Session mode, behaves like a direct Postgres connection
-- `POOLER_PROXY_PORT_TRANSACTION` (default: 6543) - Transaction mode, uses connection pooling
-
-For more information on configuring and using Supavisor, see the [Supavisor documentation](https://supabase.github.io/supavisor/).
-
-### Exposing your Postgres database
-
-By default, Postgres is only accessible through Supavisor. If you need direct access to the database (bypassing the connection pooler), you need to disable Supavisor and expose the Postgres port.
-
-
-
- Exposing Postgres directly bypasses connection pooling and exposes your database to the network. Configure firewall rules or network policies to restrict access to trusted IPs only.
-
-
-
-Edit `docker-compose.yml`:
-
-1. **Disable Supavisor** - Comment out or remove the entire `supavisor` service section
-2. **Expose Postgres port** - Add the port mapping to the `db` service, it should look like the example below:
-
-```yaml name=docker-compose.yml
-db:
- ports:
- - ${POSTGRES_PORT}:${POSTGRES_PORT}
- container_name: supabase-db
-```
-
-After restarting, you can connect to the database directly using a standard Postgres connection string:
-
-```sh
-postgres://postgres:[POSTGRES_PASSWORD]@[your-server-ip]:5432/[POSTGRES_DB]
-```
-
### Setting log_min_messages in Postgres
By default, the database's `log_min_messages` configuration is set to `fatal` in [docker-compose.yml](https://github.com/supabase/supabase/blob/df8729a82b1847e2989c14ede27965612761d503/docker/docker-compose.yml#L466) to prevent redundant logs generated by Realtime. You can configure `log_min_messages` using any of the Postgres [Severity Levels](https://www.postgresql.org/docs/current/runtime-config-logging.html#RUNTIME-CONFIG-SEVERITY-LEVELS).
diff --git a/apps/docs/content/guides/self-hosting/restore-from-platform.mdx b/apps/docs/content/guides/self-hosting/restore-from-platform.mdx
index b0ccb6211748c..f13530e00ae87 100644
--- a/apps/docs/content/guides/self-hosting/restore-from-platform.mdx
+++ b/apps/docs/content/guides/self-hosting/restore-from-platform.mdx
@@ -52,7 +52,7 @@ Before restoring, check the following on your self-hosted instance:
## Step 4: Restore to your self-hosted database
-Connect to your self-hosted Postgres and restore the dump files. The [default](/docs/guides/self-hosting/docker#accessing-postgres) connection string for self-hosted Supabase is:
+Connect to your self-hosted Postgres and restore the dump files. The [default](/docs/guides/self-hosting/accessing-postgres#connect-through-supavisor) connection string for self-hosted Supabase is:
```
postgres://postgres.your-tenant-id:[POSTGRES_PASSWORD]@[your-domain]:5432/postgres
@@ -166,7 +166,7 @@ select * from pg_available_extensions;
### Connection refused
-Make sure your self-hosted Postgres port is accessible. In the default [self-hosted Supabase](/docs/guides/self-hosting/docker#accessing-postgres) setup, the user is `postgres.your-tenant-id` with Supavisor on port `5432`.
+Make sure your self-hosted Postgres port is accessible. In the default [self-hosted Supabase](/docs/guides/self-hosting/accessing-postgres#connect-through-supavisor) setup, the user is `postgres.your-tenant-id` with Supavisor on port `5432`.
### Legacy Studio configuration
diff --git a/apps/docs/features/docs/Troubleshooting.page.tsx b/apps/docs/features/docs/Troubleshooting.page.tsx
index 4e998f4ac44d9..438df7f4cd793 100644
--- a/apps/docs/features/docs/Troubleshooting.page.tsx
+++ b/apps/docs/features/docs/Troubleshooting.page.tsx
@@ -12,6 +12,7 @@ export default async function TroubleshootingPage({ entry }: { entry: ITroublesh
const dateUpdated = entry.data.database_id.startsWith('pseudo-')
? new Date()
: (await getTroubleshootingUpdatedDates()).get(entry.data.database_id)
+ const errorCodes = [...new Set(entry.data.errors?.map(formatError).filter(Boolean) ?? [])]
return (
>
)}
- {entry.data.errors?.length && entry.data.errors.length > 0 && (
+ {errorCodes.length > 0 && (
<>