Skip to content

Latest commit

 

History

History
194 lines (134 loc) · 8.51 KB

supabase-self-hosted.md

File metadata and controls

194 lines (134 loc) · 8.51 KB

CloudSync Quick Start: Self-Hosted Supabase

This guide helps you enable CloudSync on a fresh or existing self-hosted Supabase instance. CloudSync adds offline-first synchronization capabilities to your PostgreSQL database.

Step 1: Use the CloudSync Supabase Image

When deploying or updating your Supabase instance, use the published CloudSync Supabase image instead of the standard Supabase Postgres image.

For New Deployments

Follow Supabase's Installing Supabase guide to set up the initial files and .env configuration. Then, before the first docker compose up -d, update your docker-compose.yml to use the CloudSync-enabled Postgres image:

db:
  # Supabase on PostgreSQL 15
  image: sqlitecloud/sqlite-sync-supabase:15
  # instead of: public.ecr.aws/supabase/postgres:15.14.1.170

  # OR Supabase on PostgreSQL 17
  image: sqlitecloud/sqlite-sync-supabase:17
  # instead of: public.ecr.aws/supabase/postgres:17.6.1.170

Use the CloudSync image tag that matches your Supabase PostgreSQL major version. :15 and :17 each track the base image Supabase currently ships for that major, so they are the standard choice.

Pinning an exact base: if your Supabase stack pins a specific base image, an exact-base CloudSync tag is published alongside the major tags (e.g. sqlitecloud/sqlite-sync-supabase:17.6.1.170). Use it when you need the CloudSync image built from precisely the base your stack expects; otherwise the major tag is simpler.

Ubuntu vs Alpine base: Supabase moved both the PG15 and PG17 lines to an Alpine userland — starting at roughly 15.14.1.x and 17.6.1.084 respectively; earlier bases in both lines were Ubuntu. The extension is identical on either — the PostgreSQL binary is glibc-linked in both families — so for a new deployment the difference does not matter. Moving an existing data directory from one family to the other needs the ownership fix described under For Existing Deployments.

Add the CloudSync Init Script

Create the init SQL:

mkdir -p volumes/db
cat > volumes/db/cloudsync.sql << 'EOF'
CREATE EXTENSION IF NOT EXISTS cloudsync;
EOF

Add a volume mount to the db service in docker-compose.yml:

services:
  db:
    volumes:
      # ... existing volume mounts ...
      - ./volumes/db/cloudsync.sql:/docker-entrypoint-initdb.d/init-scripts/100-cloudsync.sql:Z

The 100- prefix ensures CloudSync loads after Supabase's own init scripts, which are typically numbered 98-99 in the self-hosted Docker Compose setup.

Then start Supabase:

docker compose pull
docker compose up -d

For Existing Deployments

Follow Supabase's Updating guide. When updating the Postgres image, replace the default image with the matching CloudSync image:

# Update docker-compose.yml to use:
# sqlitecloud/sqlite-sync-supabase:15
# or sqlitecloud/sqlite-sync-supabase:17
docker compose pull
docker compose down && docker compose up -d

If Postgres has already been initialized and you are adding CloudSync afterward, the init script will not run automatically. Connect to the database and run:

CREATE EXTENSION IF NOT EXISTS cloudsync;

If Postgres fails to start after moving to a different base image

Supabase base images do not pin the postgres user's UID, so it differs between them — for example 105 on the Ubuntu 20.04 based 15.8.1.085, 101 on the Ubuntu 24.04 based 15.8.1.135, and 100 on the Alpine based 17.6.1.151. Discover the UID of the image you are moving to rather than assuming one. A data directory created under one base is unreadable by another, and the container fails to start with:

cat: /etc/postgresql-custom/pgsodium_root.key: Permission denied
FATAL:  invalid secret key

Re-own the two postgres-owned paths with the UID and GID of the new image:

docker compose stop db

# UID:GID of the postgres user in the new image
docker compose run --rm --no-deps --entrypoint id db postgres

chown -R <uid>:<gid> ./volumes/db/data
chown -R <uid>:<gid> "$(docker volume inspect supabase_db-config --format '{{.Mountpoint}}')"

docker compose up -d db

Take a pg_dumpall backup first: the change is one-way, and the previous image can no longer read the data directory afterwards. Only deployments whose volumes were initialized under a different base are affected; new deployments are not.


Step 2: Verify the Extension

docker compose exec db psql -U supabase_admin -d postgres -c "SELECT cloudsync_version();"

If the extension is installed correctly, PostgreSQL returns the CloudSync version string.

Upgrading a later release

CloudSync uses the first two components of its semver as the PostgreSQL extension version (for example, 1.0.17 installs as extension version 1.0). How you upgrade depends on which component changed:

  • PATCH release (e.g. 1.0.17 → 1.0.18): pull the matching sqlitecloud/sqlite-sync-supabase:<tag> image and restart the db service. No SQL-level upgrade is needed — installed_version stays at 1.0 and the new binary takes over on reconnect. SELECT cloudsync_version(); confirms the new semver.

  • MINOR or MAJOR release (e.g. 1.0.x → 1.1.0): pull the new image and restart as above, then run once per database:

    ALTER EXTENSION cloudsync UPDATE;

    PostgreSQL applies any cloudsync--<from>--<to>.sql upgrade scripts shipped with the release and moves installed_version to the new value.

You can check the current state at any time:

SELECT name, default_version, installed_version
FROM pg_available_extensions
WHERE name = 'cloudsync';

If installed_version is behind default_version after a release, run ALTER EXTENSION cloudsync UPDATE; to catch up.


Step 3: Register Your Database in the CloudSync Dashboard

In the CloudSync dashboard, create a new workspace with the Supabase (Self-hosted) provider, then add a project with your PostgreSQL connection string:

postgresql://user:password@host:5432/database

Step 4: Enable CloudSync on Tables

In the dashboard, go to the Database Setup tab, select the tables you want to sync, and click Deploy Changes.


Step 5: Set Up Authentication

On the Client Integration tab you'll find your Database ID and authentication settings.

Quick Test with API Key (Recommended for Testing)

The fastest way to test CloudSync without per-user access control — no JWT setup needed.

With API key authentication, CloudSync uses the database role resolved from the API-key-authenticated connection when available; otherwise it falls back to the role from the connection string.

SELECT cloudsync_network_init('<database-id>');
SELECT cloudsync_network_set_apikey('<username>:<password>');
SELECT cloudsync_network_sync();

Using JWT Tokens (For RLS and Production)

  1. Set Row Level Security to Yes, enforce RLS
  2. Under Authentication (JWT), click Configure authentication and choose:
    • HMAC Secret (HS256):
      • Enter your JWT_SECRET from Supabase's .env
      • Optionally add Expected audiences. When configured, a token's aud claim must contain at least one of the configured audience values.
    • JWKS Issuer Validation:
      • Enter the issuer base URL from your token's iss claim (for example https://your-auth-domain)
      • By default, CloudSync uses OIDC discovery: it requests <issuer>/.well-known/openid-configuration and reads the returned jwks_uri
      • Optionally set an Explicit JWKS URI to bypass OIDC discovery and use a specific JWKS endpoint directly. This must be a full HTTPS URI.
      • Optionally add Expected audiences. When configured, a token's aud claim must contain at least one of the configured audience values.
  3. CloudSync validates JWTs as follows:
    • HS256: uses the configured JWT secret
    • JWKS: uses the explicit jwksUri when provided; otherwise CloudSync requests <issuer>/.well-known/openid-configuration and reads jwks_uri
    • CloudSync does not fall back directly to <issuer>/.well-known/jwks.json when discovery is used
  4. For claim details and RLS examples, see:
  5. In your client code:
    SELECT cloudsync_network_init('<database-id>');
    SELECT cloudsync_network_set_token('<jwt-token>');
    SELECT cloudsync_network_sync();