Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
4adf551
fix: audit logs project filtering hide all projects logs (#44620)
djhi Apr 7, 2026
4a5d41a
fix: error link (#44466)
7ttp Apr 7, 2026
3f9765e
refactor(studio): migrate all react-contexify usage to ContextMenu_Sh…
ivasilov Apr 7, 2026
81aefa3
fix(docs): install @redocly/cli dep to apps/docs (#44624)
mandarini Apr 7, 2026
9e97aea
feat: update @supabase/*-js libraries to v2.102.1 (#44623)
supabase-supabase-autofixer[bot] Apr 7, 2026
00ecdca
feat(studio): add admonition for public bucket rls (#44447)
kemaldotearth Apr 7, 2026
d9c7f2d
fix(docs): highlight supabase skills on the agent skills docs page (#…
Rodriguespn Apr 7, 2026
f4ba8dd
Update connecting-to-postgres.mdx (#44584)
lch-supa Apr 7, 2026
8923d52
chore: use dndkit for table editor columns sorting (#44617)
djhi Apr 7, 2026
acb0929
chore: migrate row sort in SupabaseGrid to DndKit (#44628)
djhi Apr 7, 2026
ee9ecdf
fix mgmt api examples for ipv4 addon (#44547)
Ellba Apr 7, 2026
bf16d7f
fix(reports): fix report block styling issues FE-2844 (#44436)
jordienr Apr 7, 2026
6e580bf
fix(studio): respect enabled prop in index advisor query hook (#44588)
oniani1 Apr 7, 2026
d7a9ded
add pg17 upgrade configuration for self-hosted supabase (#44147)
aantti Apr 7, 2026
4a41db5
docs: update js sdk docs (2.102.1) (#44626)
supabase-supabase-autofixer[bot] Apr 7, 2026
5d02142
docs: clarify pause and restore uses physical backup (#44580)
tina-wh Apr 7, 2026
550f1a7
fix: policy dropdowns (#44568)
7ttp Apr 7, 2026
4696fc2
refactor: start migrating pgmeta functions to return SafeSqlFragment …
charislam Apr 7, 2026
9925306
feat(studio): hide overlay on test edge function sheet (#44627)
kemaldotearth Apr 7, 2026
f6eb9a0
feat: refresh tax preview on address change (#44470)
ignaciodob Apr 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2856,6 +2856,7 @@ export const self_hosting: NavMenuConstant = {
name: 'Add Reverse Proxy with HTTPS',
url: '/guides/self-hosting/self-hosted-proxy-https',
},
{ name: 'Upgrade to Postgres 17', url: '/guides/self-hosting/postgres-upgrade-17' },
{
name: 'Restore Project from Platform',
url: '/guides/self-hosting/restore-from-platform',
Expand Down
6 changes: 0 additions & 6 deletions apps/docs/content/guides/database/connecting-to-postgres.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -121,12 +121,6 @@ The Dedicated Pooler ensures best performance and latency, while using up more o

Get your project's Dedicated pooler connection string from your project dashboard by clicking [Connect](/dashboard/project/_?showConnect=true&method=transaction).

<Admonition type="note">

PgBouncer always runs in Transaction mode and the current version does not support prepared statement (will be added in a few weeks).

</Admonition>

## More about connection pooling

Connection pooling improves database performance by reusing existing connections between queries. This reduces the overhead of establishing connections and improves scalability.
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/content/guides/getting-started/ai-skills.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ You can also install the skills as Claude Code plugins:

```bash
/plugin marketplace add supabase/agent-skills
/plugin install postgres-best-practices@supabase-agent-skills
/plugin install supabase@supabase-agent-skills
```

Skills work with 18+ AI agents including Claude Code, GitHub Copilot, Cursor, Cline, and many others.
Expand Down
5 changes: 3 additions & 2 deletions apps/docs/content/guides/platform/ipv4-address.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,16 @@ curl -X GET "https://api.supabase.com/v1/projects/$PROJECT_REF/billing/addons" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN"

# Enable IPv4 add-on
curl -X POST "https://api.supabase.com/v1/projects/$PROJECT_REF/addons" \
curl -X PATCH "https://api.supabase.com/v1/projects/$PROJECT_REF/billing/addons" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"addon_variant": "ipv4_default",
"addon_type": "ipv4"
}'

# Disable IPv4 add-on
curl -X DELETE "https://api.supabase.com/v1/projects/$PROJECT_REF/billing/addons/ipv4" \
curl -X DELETE "https://api.supabase.com/v1/projects/$PROJECT_REF/billing/addons/ipv4_default" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN"
```

Expand Down
8 changes: 4 additions & 4 deletions apps/docs/content/guides/platform/upgrading.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,12 @@ We recommend using the In-place upgrade method, as it is faster, and more reliab

</Admonition>

When you pause and restore a project, the restored database includes the latest features. This method _does_ include downtime, so be aware that your project will be inaccessible for a short period of time.
When you pause and restore a project, the restored database includes the latest features. **This method includes downtime**, so be aware that your project will be inaccessible for a short period of time.

1. On the [General Settings](/dashboard/project/_/settings/general) page in the Dashboard, click **Pause project**. You will be redirected to the home screen as your project is pausing. This process can take several minutes.
1. After your project is paused, click **Restore project**. The restoration can take several minutes depending on how much data your database has. You will receive an email once the restoration is complete.
1. On the [General Settings](/dashboard/project/_/settings/general) page in the Dashboard, click **Pause project**. You will be redirected to the home screen in the meantime.
1. After this, click **Restore project**. Your project will be restored from the [physical backup](/guides/platform/backups). You should receive an email once the restoration is complete.

Note that a pause + restore upgrade involves tearing down your project's resources before bringing them back up again. If the restore process should fail, manual intervention from Supabase support will be required to bring your project back online.
Pausing and restoring project will take some time depending on how much data your database has. If the restore process fails, [contact Supabase support](/dashboard/support/new?projectRef=) to bring your project back online.

## Caveats

Expand Down
2 changes: 1 addition & 1 deletion apps/docs/content/guides/realtime/reports.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ height={645}
| Configure limits | Adjust "Max concurrent connections" or "Max events per second" settings if errors are related to quota limits being reached | [Realtime Settings Guide](/docs/guides/realtime/settings) |
| Check logs | Investigate specific error messages, error codes, and request details in your project dashboard | [Realtime Logs Dashboard](/dashboard/project/_/database/realtime-logs) |
| Review request volume | Compare error rates with total request volume to calculate error percentages and identify trends | [Total Requests Report](#total-requests) |
| Understand error codes | Understand specific error codes and their resolutions | [Realtime Error Codes Reference](/docs/reference/realtime/error-codes) |
| Understand error codes | Understand specific error codes and their resolutions | [Realtime Error Codes Reference](/docs/guides/realtime/error_codes) |
| Learn HTTP status codes | Learn about HTTP status codes including 4XX client errors and 5XX server errors | [HTTP Status Codes Troubleshooting](/docs/troubleshooting/http-status-codes) |
| Fix timeout errors | Resolve WebSocket timeout errors caused by Node.js version incompatibility | [TIMED_OUT Connection Errors Troubleshooting](/docs/troubleshooting/realtime-connections-timed_out-status) |
| Understand heartbeats | Monitor heartbeat status to detect connection issues and handle timeouts | [Realtime Heartbeats Guide](/docs/troubleshooting/realtime-heartbeat-messages) |
Expand Down
320 changes: 320 additions & 0 deletions apps/docs/content/guides/self-hosting/postgres-upgrade-17.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,320 @@
---
title: 'Upgrade to Postgres 17'
description: 'Start a new self-hosted Supabase deployment with Postgres 17, or upgrade an existing Postgres 15 installation.'
subtitle: 'Start a new self-hosted Supabase deployment with Postgres 17, or upgrade an existing Postgres 15 installation.'
---

Self-hosted Supabase ships with Postgres 15 by default. This guide covers two scenarios:

- **New deployment** - start fresh with Postgres 17 (no existing data)
- **Upgrade existing deployment** - migrate from Postgres 15 to Postgres 17 using `pg_upgrade`

## Before you begin

- Complete the [Self-Hosting with Docker](/docs/guides/self-hosting/docker) setup
- Your current database image should be `supabase/postgres:15.x` (check with `docker inspect supabase-db --format '{{.Config.Image}}'`)

## New deployment with Postgres 17

If you are starting a new self-hosted Supabase instance with **no existing data**, use the Postgres 17 compose override:

```sh
docker compose -f docker-compose.yml -f docker-compose.pg17.yml up -d
```

This uses the `docker-compose.pg17.yml` override file which swaps the database image:

```yaml
services:
db:
image: supabase/postgres:17.6.1.084
```

<Admonition type="tip">

Always include both compose files when running commands. If you omit the override, Docker Compose falls back to the Postgres 15 image defined in `docker-compose.yml`.

</Admonition>

The rest of the setup is the same as the standard Docker guide. All init scripts (`roles.sql`, `jwt.sql`, `webhooks.sql`, etc.) are compatible with Postgres 17.

{/* supa-mdx-lint-disable-next-line Rule003Spelling */}
If the new Postgres 17 container fails to start, make sure to check for an old `db-config` Docker volume. See [Postgres 17 fails to start with a leftover db-config volume](#postgres-17-fails-to-start-with-a-leftover-db-config-volume) for details.

## Upgrade an existing Postgres 15 deployment

Upgrading an existing deployment uses `pg_upgrade` to migrate data in place. The included upgrade scripts automates the full process.

### What the upgrade does

1. Pulls a specific Postgres 17 image and extracts upgrade binaries
2. Pulls supplemental upgrade scripts from Supabase's [Postgres](https://github.com/supabase/postgres) repository
3. Stops all self-hosted Supabase containers
4. Runs `pg_upgrade` inside a temporary Postgres 15 container
5. Runs additional tasks inside a temporary Postgres 17 container (re-enables extensions, applies patches, runs `VACUUM ANALYZE`)
6. Swaps data directories (the original is kept as a backup)
7. Starts self-hosted Supabase with Postgres 17
8. Applies additional role migrations (new in Postgres 17)

### Create a backup

<Admonition type="danger" label="Back up your data before upgrading">

You should create your own independent backup in case of disk failure or other issues.

</Admonition>

The upgrade script automatically preserves the pgsodium key and original data directory as `./volumes/db/data.bak.pg15` as the final step. However, it is recommended to **always** create your own independent backup before starting:

Back up the database data directory:

```sh
cp -a ./volumes/db/data ./volumes/db/data-manual-backup
```

Back up the pgsodium encryption key (stored in a Docker named volume):

```sh
docker compose run --rm db cat /etc/postgresql-custom/pgsodium_root.key > ./pgsodium_root.key.backup
```

<Admonition type="caution">

The `db-config` Docker named volume contains the pgsodium root encryption key. If you lose this key and have vault secrets, they become unrecoverable. The `cp -a` above backs up the data directory but NOT the named volume.

</Admonition>

Optionally, take a logical backup too:

```sh
docker exec supabase-db pg_dumpall -h localhost -U supabase_admin > ./pg15_dump.sql
```

### Requirements

- At least **2x your current database size + 5 GB** of free disk space (`pg_upgrade` copies the data directory; the upgrade tarball is ~1.2 GB compressed)
- The script prompts for confirmation at each major step (use `--yes` to skip prompts)
- All self-hosted Supabase containers must be running before starting the upgrade
- Requires `bash`
- Must be run as root or using `sudo`

### Extensions removed in Postgres 17

The following extensions are **not available** in Postgres 17 builds. The upgrade script will prompt you to drop them if any of these are found:

| Extension | Notes |
| ------------- | ------------------------- |
| `timescaledb` | Not built for Postgres 17 |
| `plv8` | Not built for Postgres 17 |
| `plcoffee` | Companion to plv8 |
| `plls` | Companion to plv8 |

None of the above extensions are installed by default in the self-hosted Supabase setup. If you have installed any of them manually and need to keep them, **do not proceed** with the upgrade.

### Run the upgrade

```sh
sudo bash utils/upgrade-pg17.sh
```

The script might require your confirmation at some steps (e.g., while checking for disk space, or whether to disable extensions, or remove previous backups).

### After the upgrade

After a successful upgrade, always use both compose files:

```sh
docker compose -f docker-compose.yml -f docker-compose.pg17.yml up -d
```

To verify that Postgres 17 is running:

```sh
docker compose -f docker-compose.yml -f docker-compose.pg17.yml exec db psql -U postgres -c "SELECT version();"
```

The original Postgres 15 data is preserved at `./volumes/db/data.bak.pg15`. The pgsodium root key is saved as `./volumes/db/pgsodium_root.key.bak.pg15`. The upgrade binaries tarball is cached at `./volumes/db/pg17_upgrade_bin_*.tar.gz`. Once you have verified that everything works, you can reclaim disk space:

```sh
rm -rf ./volumes/db/data.bak.pg15 ./volumes/db/pgsodium_root.key.bak.pg15 ./volumes/db/pg17_upgrade_bin_*.tar.gz
```

<Admonition type="caution">

Do not delete `data.bak.pg15` until you have verified the upgrade. Rollback is only possible while the backup exists.

</Admonition>

### Rollback

If you need to revert to Postgres 15 (run the following commands as root):

```sh
docker compose -f docker-compose.yml -f docker-compose.pg17.yml down && \
rm -rf ./volumes/db/data && \
mv ./volumes/db/data.bak.pg15 ./volumes/db/data && \
docker compose run --rm db chown -R postgres:postgres /etc/postgresql-custom/ && \
docker compose up -d
```

This restores the original data directory, fixes file ownership on the `db-config` volume (Supabase's Postgres 15 and 17 images use different user IDs), and starts with the old Postgres 15 image.

### Custom Postgres configuration

The Postgres 17 image loads any `.conf` files from `/etc/postgresql-custom/conf.d/` on startup. This directory is on the `db-config` named volume, so changes persist across restarts.

<Admonition type="note">

This is a Supabase Postgres 17 image feature. The Postgres 15 image does not load files from `conf.d/`.

</Admonition>

To add custom Postgres settings, create a `.conf` file in the volume. Since `conf.d/` is on a Docker named volume (not a bind mount), you need to write through the container:

```sh
docker exec supabase-db bash -c 'cat > /etc/postgresql-custom/conf.d/custom.conf << EOF
max_connections = 200
EOF'
```

Restart to apply (`max_connections` requires a full restart):

```sh
docker compose -f docker-compose.yml -f docker-compose.pg17.yml restart db
```

Verify the new settings:

```sh
docker compose -f docker-compose.yml -f docker-compose.pg17.yml exec db psql -U postgres -c "SHOW max_connections;"
```

### Upgrade process details

The upgrade script delegates the core migration work to two scripts from the [supabase/postgres](https://github.com/supabase/postgres) repository (`ansible/files/admin_api_scripts/pg_upgrade_scripts/`).

**Phase 1 - Migrate data (Postgres 15 container):**

1. Disables extensions that are incompatible with `pg_upgrade` (`pg_graphql`, `pg_stat_monitor`, `pg_backtrace`, `wrappers`, `pgrouting`) and generates SQL to re-enable them after the upgrade
2. Temporarily grants superuser to the `postgres` role (required by `pg_upgrade`)
3. Extracts the previously saved Postgres 17 binaries tarball and runs `initdb` to create a new empty database
4. Runs `pg_upgrade --check` to verify the upgrade can succeed before making changes
5. Stops Postgres 15 and runs `pg_upgrade` to migrate all data to the new database
6. Copies Postgres configuration and the SQL scripts generated by `pg_upgrade` to a staging directory for the next phase

**Phase 2 - Finalize (Postgres 17 container):**

1. Moves the upgraded data directory into place and starts Postgres 17
2. Applies extension compatibility patches for Wrappers, `pg_net`, `pg_cron`, and Vault (fixes ownership, grants, and foreign server options)
3. Runs the SQL scripts generated by `pg_upgrade` to update system catalogs and extension versions
4. Re-enables the extensions that were disabled in phase 1
5. Grants predefined roles (`pg_monitor`, `pg_read_all_data`, `pg_signal_backend`, and on Postgres 16+ also `pg_create_subscription`) and revokes the temporary superuser grant
6. Restarts Postgres and runs `vacuumdb --all --analyze-in-stages` to rebuild optimizer statistics

After both phases complete, the upgrade script preserves the original Postgres 15 data directory as a backup and starts the full Supabase stack with Postgres 17.

## Troubleshooting

{/* supa-mdx-lint-disable-next-line Rule001HeadingCase */}

### pg_upgrade fails with replication slot errors

`pg_upgrade` cannot proceed if there are active replication slots. Default self-hosted installs don't have any, but if you set up logical replication or have custom replication configurations, drop the slots before upgrading:

```sh
docker exec supabase-db psql -h localhost -U supabase_admin -d postgres -c "
SELECT pg_drop_replication_slot(slot_name)
FROM pg_replication_slots;
"
```

Then re-run the upgrade script. Replication slots will need to be manually recreated after the upgrade.

### "Permission denied" on the data directory

The upgrade script fixes file ownership automatically (Postgres 15 and 17 use different UIDs). If you still see permission errors, run:

```sh
docker compose -f docker-compose.yml -f docker-compose.pg17.yml run --rm db \
chown -R postgres:postgres /var/lib/postgresql/data
```

{/* supa-mdx-lint-disable-next-line Rule001HeadingCase */}

### pgsodium / Supabase Vault errors

The `db-config` named volume contains the pgsodium root encryption key at `/etc/postgresql-custom/pgsodium_root.key`. This volume is preserved during the upgrade. Never run `docker compose down -v` as this destroys named volumes and makes vault secrets unrecoverable.

### Services fail to connect after upgrade

Restart all services to pick up the new database:

```sh
docker compose -f docker-compose.yml -f docker-compose.pg17.yml down && \
docker compose -f docker-compose.yml -f docker-compose.pg17.yml up -d
```

### Disk space issues during upgrade

The upgrade needs space for:

- The upgrade tarball (~1.2 GB compressed, cached for re-runs)
- A full copy of your database (created by `pg_upgrade`)
- The original data (kept as backup)

The script uses `/tmp` (or `TMPDIR` if set) for its staging directory, which holds the downloaded tarball and upgrade scripts. If your `/tmp` filesystem is small or mounted with limited space, you can point it to a different location, e.g.:

```sh
sudo TMPDIR=/mnt/my-tmp bash utils/upgrade-pg17.sh
```

If you run out of space mid-upgrade, the safest path is to roll back and free up disk space before retrying.

{/* supa-mdx-lint-disable-next-line Rule003Spelling */}

### Postgres 17 fails to start with a leftover db-config volume

If you are starting a **fresh** Postgres 17 deployment (not upgrading from Postgres 15) and the container fails to start, the most likely cause is a leftover `db-config` volume from a previous Postgres 15 installation. Try to start the containers without the `-d` option and/or check the logs for errors about `postgresql.conf` or other configuration mismatch.

To fix, remove the old volume and let Postgres 17 initialize a clean configuration:

```sh
docker compose -f docker-compose.yml -f docker-compose.pg17.yml down && \
docker volume rm $(docker volume ls --filter "name=db-config" --format '{{.Name}}') && \
docker compose -f docker-compose.yml -f docker-compose.pg17.yml up -d
```

<Admonition type="caution">

Removing the `db-config` volume destroys any custom Postgres configuration and the pgsodium root key. Only do this for fresh installations with no existing data or vault secrets.

</Admonition>

### Restoring from a manual backup

If the upgrade fails and the script's built-in rollback isn't sufficient, restore from the manual backups created in the [Create a backup](#create-a-backup) step:

Restore the data:

```sh
docker compose -f docker-compose.yml -f docker-compose.pg17.yml down && \
rm -rf ./volumes/db/data && \
cp -a ./volumes/db/data-manual-backup ./volumes/db/data
```

Restore the pgsodium key to the `db-config` volume:

```sh
docker compose run --rm db \
sh -c 'cat > /etc/postgresql-custom/pgsodium_root.key' < ./pgsodium_root.key.backup && \
docker compose run --rm db \
chown postgres:postgres /etc/postgresql-custom/pgsodium_root.key && \
docker compose run --rm db \
chmod 600 /etc/postgresql-custom/pgsodium_root.key
```

Start Postgres 15:

```sh
docker compose up -d
```
Loading
Loading