feat(self-hosted): add pgbouncer override (#49052)

This commit is contained in:
Inder Singh authored and GitHub committed 2026-08-20 11:21:54 +02:00
1 parent facc2df09e
commit 768cf11b9c
5 files changed
+123 -7

No files matched your search

+3 -1
View File
@@ -20,7 +20,7 @@ jobs:
strategy:
fail-fast: false
matrix:
config: [default, logs, kong, rustfs, kong-rustfs]
config: [default, logs, kong, rustfs, kong-rustfs, pgbouncer]
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
@@ -57,6 +57,8 @@ jobs:
docker compose -f docker-compose.yml -f docker-compose.rustfs.yml up --quiet-pull --wait --wait-timeout 180
elif [ "${{ matrix.config }}" = "kong-rustfs" ]; then
docker compose -f docker-compose.yml -f docker-compose.kong.yml -f docker-compose.rustfs.yml up --quiet-pull --wait --wait-timeout 180
elif [ "${{ matrix.config }}" = "pgbouncer" ]; then
docker compose -f docker-compose.yml -f docker-compose.pgbouncer.yml up --quiet-pull --wait --wait-timeout 180
else
docker compose up --quiet-pull --wait --wait-timeout 180
fi
+8 -3
View File
@@ -119,19 +119,24 @@ POSTGRES_PORT=5432
############
# Supavisor - Database pooler
# Database pooler
############
# Self-hosted Supabase uses Supavisor as the default database pooler.
# If you use the PgBouncer docker-compose override, Supavisor is disabled
# and the pooler settings below are used to configure PgBouncer instead.
#
# Supavisor exposes POSTGRES_PORT and POOLER_PROXY_PORT_TRANSACTION,
# POSTGRES_PORT is used for session mode pooling
# PgBouncer only exposes POOLER_PROXY_PORT_TRANSACTION.
#
# Port to use for transaction mode pooling connections
POOLER_PROXY_PORT_TRANSACTION=6543
# Maximum number of PostgreSQL connections Supavisor opens per pool
# Maximum number of PostgreSQL connections Supavisor or PgBouncer opens per pool
POOLER_DEFAULT_POOL_SIZE=20
# Maximum number of client connections Supavisor accepts per pool
# Maximum number of client connections Supavisor or PgBouncer accepts per pool
POOLER_MAX_CLIENT_CONN=100
# Unique Supavisor tenant identifier
+56
View File
@@ -0,0 +1,56 @@
# PgBouncer override for self-hosted Supabase
#
# Replaces the default Supavisor pooler with PgBouncer in transaction mode.
#
# Usage:
# docker compose -f docker-compose.yml -f docker-compose.pgbouncer.yml up -d
#
# Or use run.sh to add PgBouncer to the stack:
# sh run.sh config add pgbouncer
# sh run.sh start
#
services:
supavisor: !reset null
# To expose Postgres directly on POSTGRES_PORT (5432), uncomment the "db"
# section below.
# WARNING: this opens Postgres to the external traffic. Restrict access at the
# network level if necessary.
#db:
# ports:
# - ${POSTGRES_PORT}:${POSTGRES_PORT}
pgbouncer:
container_name: supabase-pgbouncer
image: edoburu/pgbouncer:v1.25.2-p0
restart: unless-stopped
ports:
- ${POOLER_PROXY_PORT_TRANSACTION}:6432
healthcheck:
test: ['CMD', 'pg_isready', '-h', 'localhost', '-p', '6432']
interval: 10s
timeout: 5s
retries: 3
start_period: 5s
depends_on:
db:
condition: service_healthy
environment:
# PgBouncer connects to Postgres as the dedicated `pgbouncer` role and
# looks up every other role's password on demand via the auth_query below.
DB_USER: pgbouncer
DB_PASSWORD: ${POSTGRES_PASSWORD}
DB_HOST: ${POSTGRES_HOST}
DB_PORT: ${POSTGRES_PORT}
DB_NAME: ${POSTGRES_DB}
LISTEN_PORT: 6432
AUTH_TYPE: scram-sha-256
AUTH_QUERY: SELECT * FROM pgbouncer.get_auth($$1)
# POOL_MODE can be: session, transaction, or statement
POOL_MODE: transaction
DEFAULT_POOL_SIZE: ${POOLER_DEFAULT_POOL_SIZE}
MAX_CLIENT_CONN: ${POOLER_MAX_CLIENT_CONN}
# Only the `pgbouncer` role may run get_auth / the admin console.
ADMIN_USERS: pgbouncer
STATS_USERS: pgbouncer
+13 -3
View File
@@ -148,9 +148,19 @@ check_logs_if_running analytics \
'Executing startup tasks' \
'Ensuring single tenant user is seeded'
check_logs supavisor \
'Connected to Postgres database' \
'HEAD /api/health$'
# Database pooler: Supavisor by default, or PgBouncer when the pgbouncer
# override is enabled (which disables Supavisor). Check whichever is running.
if is_service_running supavisor; then
check_logs supavisor \
'Connected to Postgres database' \
'HEAD /api/health$'
elif is_service_running pgbouncer; then
check_logs pgbouncer \
'process up: PgBouncer' \
'listening on .*6432'
else
fail_msg "pooler (neither supavisor nor pgbouncer is running)"
fi
check_logs_if_running vector \
'Vector has started'
+43
View File
@@ -77,6 +77,16 @@ http_body() {
curl -s "$@" "$url"
}
# Is a compose service running? Falls back to a label lookup so it works
# regardless of which override files are loaded in this shell.
service_running() {
svc="$1"
docker compose ps --services --status running 2>/dev/null | grep -qx "$svc" && return 0
docker ps --filter "label=com.docker.compose.project=${COMPOSE_PROJECT_NAME:-supabase}" \
--filter "label=com.docker.compose.service=$svc" \
--filter "status=running" --quiet | grep -q '.'
}
echo ""
echo "=== Self-hosted smoke test against $BASE_URL ==="
echo ""
@@ -487,6 +497,39 @@ check "Realtime /api/openapi blocked" "403" \
"$(http_status "$BASE_URL/realtime/v1/api/openapi" \
-H "apikey: $ANON_KEY")"
# ---------------------------------------------
# 10. Database pooler (transaction mode)
# ---------------------------------------------
echo ""
echo "--- Database pooler (transaction mode) ---"
if command -v docker >/dev/null 2>&1; then
pg_password=$(grep '^POSTGRES_PASSWORD=' .env | cut -d= -f2-)
pooler_tenant_id=$(grep '^POOLER_TENANT_ID=' .env | cut -d= -f2-)
# Connect as the postgres role through whichever transaction pooler is running:
# Supavisor (default) -> service 'supavisor', port 6543, user 'postgres.<tenant>'
# PgBouncer (override) -> service 'pgbouncer', port 6432 (published as 6543), user 'postgres'
# psql runs inside the db container (always has a client) and reaches the
# pooler over the compose network.
if service_running supavisor; then
pooler_user="postgres.$pooler_tenant_id"; pooler_host="supavisor"; pooler_port=6543
elif service_running pgbouncer; then
pooler_user="postgres"; pooler_host="pgbouncer"; pooler_port=6432
else
pooler_user=""
fi
if [ -n "$pooler_user" ]; then
pooler_result=$(docker exec -e PGPASSWORD="$pg_password" supabase-db \
psql "host=$pooler_host port=$pooler_port user=$pooler_user dbname=postgres sslmode=disable" \
-tAc "select 'pooler_ok';" 2>/dev/null | tr -d '[:space:]')
check "Pooler transaction-mode query (as postgres)" "pooler_ok" "$pooler_result"
else
check "Pooler running (supavisor or pgbouncer)" "true" "false"
fi
else
echo " SKIP: docker not available"
fi
# ---------------------------------------------
# Summary
# ---------------------------------------------