Pocketbase vs Supabase: Self-Hosted 2026
Pocketbase vs Supabase: Self-Hosted 2026
TL;DR
Choosing between PocketBase and Supabase for self-hosting in 2026 comes down to one fundamental trade-off: operational simplicity versus platform capability. PocketBase is a 15 MB Go binary — download it, run it, done in 30 seconds, and it idles at 50 MB RAM with zero containers. Supabase self-hosted is a Docker Compose stack of 7+ containers (Kong gateway, GoTrue auth, PostgREST, Realtime, Storage API, Postgres, and Meta) requiring at minimum 4 GB RAM and 15–20 minutes to configure. Both replace Firebase. Neither choice is wrong — PocketBase wins for projects where one server is enough, and Supabase wins for anything that needs PostgreSQL's full power or plans to scale horizontally.
Key Takeaways
- PocketBase deployment: single binary, 30-second install, no Docker required, works on a $4/month VPS with 512 MB RAM
- Supabase deployment: 7+ Docker containers, 4 GB RAM minimum, 10–20 minutes to configure secrets and environment, requires Docker Compose v2
- Auth parity: both support email/password + OAuth providers; Supabase adds magic links, phone OTP, and enterprise SSO (SAML); PocketBase covers 30+ OAuth providers with zero config
- Realtime: PocketBase uses Server-Sent Events (SSE), simpler to proxy; Supabase uses WebSocket via Phoenix channels, more scalable but heavier to operate
- Row-level security: Supabase enforces RLS natively in PostgreSQL; PocketBase uses collection-level filter rules in the admin UI — less expressive but sufficient for most apps
- Type generation: both offer TypeScript type generation from schema; Supabase's CLI tooling is more mature; PocketBase's
pocketbase-typegenpackage works well - Dart/Flutter SDK: PocketBase has a first-class Dart SDK maintained by the core team; Supabase's Flutter SDK is equally mature — this is not a differentiator
Why the Self-Hosting Experience Matters in 2026
Self-hosting a backend is not a one-time event. You are committing to an ongoing operational relationship: deployments, upgrades, backups, SSL renewals, secret rotation, and incident response. The self-hosting experience — how many moving parts you manage, how updates work, how failures manifest — is as important as the feature set.
For PocketBase and Supabase, the self-hosting experience could not be more different. Supabase Cloud (the managed service) has a generous free tier (500 MB database, 5 GB storage, 50,000 MAU) and a $25/month Pro plan. Many teams self-host to bypass the 2 active project limit on the free tier, or to keep data on-premises for compliance reasons. PocketBase has no managed cloud equivalent — it is, by design, a tool you run yourself. The comparison here is therefore PocketBase-self-hosted vs Supabase-self-hosted, across six dimensions that matter most to operators.
PocketBase Self-Hosted: The Single Binary Reality
PocketBase's deployment story is the simplest possible: one file, one port.
Installation
# Download and run (30 seconds from zero to working backend)
wget https://github.com/pocketbase/pocketbase/releases/latest/download/pocketbase_linux_amd64.zip
unzip pocketbase_linux_amd64.zip
chmod +x pocketbase
./pocketbase serve --http="0.0.0.0:8090"
# Admin UI at http://your-server:8090/_/
# REST API at http://your-server:8090/api/
The first run creates a pb_data/ directory containing the SQLite database file, migrations history, and logs. That directory is your entire PocketBase state — back it up, and you have a complete restore.
Production Setup with Nginx and Systemd
# /etc/nginx/sites-available/api.yourdomain.com
server {
server_name api.yourdomain.com;
location / {
proxy_pass http://127.0.0.1:8090;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Required for real-time SSE connections
proxy_read_timeout 3600;
proxy_buffering off;
proxy_cache off;
}
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/api.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.yourdomain.com/privkey.pem;
}
# /etc/systemd/system/pocketbase.service
[Unit]
Description=PocketBase service
After=network.target
[Service]
Type=simple
User=pocketbase
WorkingDirectory=/opt/pocketbase
ExecStart=/opt/pocketbase/pocketbase serve --http="127.0.0.1:8090"
Restart=on-failure
RestartSec=5s
StandardOutput=append:/var/log/pocketbase.log
StandardError=append:/var/log/pocketbase.log
[Install]
WantedBy=multi-user.target
systemctl enable --now pocketbase
That is the complete production PocketBase setup. SSL via Certbot, a systemd service, and Nginx as a reverse proxy. Total configuration files: 2. Infrastructure requirements: none beyond a VPS.
Resource Requirements
| Instance Size | RAM Usage | CPU | Max MAU (estimate) |
|---|---|---|---|
| Minimal | 50–80 MB | < 0.1 vCPU idle | 1K–10K |
| Typical app | 100–200 MB | 0.1–0.3 vCPU | 10K–50K |
| High-read app | 200–400 MB | 0.5–1 vCPU | 50K–150K |
PocketBase's SQLite storage is a genuine constraint at write-heavy scale — SQLite's WAL mode handles concurrent reads excellently, but sustained concurrent writes from 100+ simultaneous clients will cause lock contention. For mobile apps with many clients writing occasional records (typical Flutter / React Native use), this limit is rarely encountered in practice.
What PocketBase Does Not Include
No Docker containers means no container orchestration, but also no isolation between PocketBase versions during upgrades (you stop and replace the binary), no built-in secrets management, and no database branching for dev/staging environments. All of these are solvable with conventional Unix tools, but they require you to solve them.
Supabase Self-Hosted: The Docker Compose Stack
Supabase self-hosted uses the official docker directory from the Supabase monorepo. The stack has grown over time — as of early 2026, a full deployment includes at minimum 7 containers.
Container Inventory
| Container | Image | Purpose |
|---|---|---|
kong | kong:2.8.1 | API gateway, routes requests to all services |
auth | supabase/gotrue:v2.140+ | Authentication server (GoTrue) |
rest | postgrest/postgrest:v12+ | Auto-generated REST API from PostgreSQL schema |
realtime | supabase/realtime:v2.27+ | WebSocket real-time (Postgres logical replication) |
storage | supabase/storage-api:v0.46+ | File storage API, S3-compatible backend |
db | supabase/postgres:15.1+ | PostgreSQL with custom extensions |
meta | supabase/postgres-meta:v0.68+ | Postgres metadata API for dashboard |
imgproxy | darthsim/imgproxy | Image transformation for storage (optional) |
analytics | supabase/logflare:1.4+ | Log ingestion (optional) |
The minimum viable self-hosted Supabase runs 7 containers. A full deployment with analytics and image transformation runs 9.
Initial Setup
# Clone the Supabase monorepo (shallow)
git clone --depth 1 https://github.com/supabase/supabase
cd supabase/docker
# Copy example environment
cp .env.example .env
Before docker compose up, you must generate and configure secrets:
# Generate JWT secret (minimum 32 chars)
export JWT_SECRET=$(openssl rand -hex 32)
echo "JWT_SECRET=$JWT_SECRET"
# Generate anon key (signed JWT with role: anon)
# Use: https://supabase.com/docs/guides/self-hosting/docker#generate-api-keys
# Or use the Supabase CLI: supabase gen keys --experimental
export ANON_KEY=$(...)
export SERVICE_ROLE_KEY=$(...)
# Generate dashboard password
export DASHBOARD_PASSWORD=$(openssl rand -base64 16)
Then edit the .env file with your domain, SMTP settings for auth emails, OAuth provider credentials, and storage configuration. A minimal .env has approximately 40 variables. Production environments add S3 credentials, SMTP relay, and SAML provider settings.
# docker-compose.yml (abbreviated key environment vars)
services:
kong:
environment:
KONG_DECLARATIVE_CONFIG: /var/lib/kong/kong.yml
auth:
environment:
GOTRUE_SITE_URL: https://app.yourdomain.com
GOTRUE_JWT_SECRET: ${JWT_SECRET}
GOTRUE_SMTP_HOST: smtp.resend.com
GOTRUE_SMTP_PORT: 587
GOTRUE_SMTP_USER: resend
GOTRUE_SMTP_PASS: ${RESEND_API_KEY}
GOTRUE_MAILER_AUTOCONFIRM: false
GOTRUE_EXTERNAL_GOOGLE_ENABLED: true
GOTRUE_EXTERNAL_GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID}
GOTRUE_EXTERNAL_GOOGLE_SECRET: ${GOOGLE_SECRET}
db:
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- supabase_db:/var/lib/postgresql/data
docker compose up -d
# Wait for health checks (30–120 seconds for all services to initialize)
docker compose ps # verify all containers are healthy
Resource Requirements
| Deployment | RAM | CPU | Disk | Monthly VPS Cost |
|---|---|---|---|---|
| Minimal (no analytics) | 4 GB | 2 vCPU | 20 GB | $24 (Hetzner CX32) |
| Standard production | 8 GB | 4 vCPU | 50 GB | $50 (Hetzner CX42) |
| Large (with analytics) | 16 GB | 8 vCPU | 100 GB | $90+ |
The PostgreSQL container alone recommends 2 GB of RAM for a production workload. Kong, GoTrue, PostgREST, and Realtime each add 200–500 MB. The 4 GB minimum is real — running Supabase self-hosted on a 2 GB VPS results in OOM kills under any meaningful load.
Deployment Complexity Head-to-Head
| Dimension | PocketBase | Supabase Self-Hosted |
|---|---|---|
| Setup time | 2–5 minutes | 15–30 minutes |
| Config files | 1 (Nginx) + systemd | .env (40+ vars) + kong.yml + compose overrides |
| Containers | 0 | 7–9 |
| Min RAM | 512 MB | 4 GB |
| Min VPS cost | $4–6/month | $24–30/month |
| SSL setup | Certbot (standard) | Handled separately (Nginx upstream of Kong) |
| Updates | Stop, replace binary, restart | docker compose pull && docker compose up -d |
| Rollback | Replace with previous binary | docker compose tag pin in compose file |
| Secrets | Environment variables / flags | .env file, 40+ variables |
| Logs | systemd journal / flat files | Per-container via Docker logs |
| Dev/staging parity | Copy pb_data/ directory | Separate Docker Compose stack |
The operational complexity gap is real. PocketBase is stateless except for pb_data/. Supabase is a distributed system — Kong routes, GoTrue manages auth state, the Realtime server maintains WebSocket connections, and PostgreSQL holds everything. Outages in self-hosted Supabase require container-level diagnosis: which service is unhealthy? Why is GoTrue refusing tokens? Why is the Realtime service dropping connections?
Feature Parity When Self-Hosted
Authentication
PocketBase auth is built into the binary. You get email/password, 30+ OAuth providers (Google, GitHub, Discord, Apple, LinkedIn, etc.), and email verification — all configured through the admin UI with no additional services.
// PocketBase: OAuth in 3 lines
const authData = await pb.collection('users').authWithOAuth2({
provider: 'google',
});
Supabase auth (GoTrue) is a separate container. It adds features PocketBase lacks: magic link authentication (passwordless), phone OTP via Twilio, enterprise SAML/SSO, and a Hooks system for custom auth logic. GoTrue is also used by other products in the ecosystem, so it has significant battle-testing.
// Supabase: same OAuth flow
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'google',
options: { redirectTo: 'https://app.yourdomain.com/auth/callback' },
});
For teams that need phone OTP or enterprise SSO, Supabase auth is the clear choice. For everything else — including Auth0 vs Clerk alternatives — both PocketBase and Supabase cover the common cases.
Realtime Subscriptions
PocketBase realtime uses Server-Sent Events (SSE). SSE is a unidirectional push mechanism over HTTP — simpler to proxy, firewall-friendly, and supported natively in browsers and most HTTP clients without additional libraries.
// PocketBase: SSE subscription
pb.collection('tasks').subscribe('*', (e) => {
console.log(e.action, e.record); // 'create' | 'update' | 'delete'
});
// Subscribe to a specific record
pb.collection('tasks').subscribe(taskId, (e) => {
setTask(e.record);
});
Subscriptions in PocketBase filter at the collection level or by record ID. You cannot subscribe to custom SQL queries. The SSE connection limit is governed by the server's file descriptor limit — at scale, you may need to tune ulimit -n and Nginx worker connections.
Supabase realtime runs as a Phoenix (Elixir) application that listens to PostgreSQL logical replication. This architecture means you can subscribe to any PostgreSQL table change — including tables not exposed via the REST API — and filter by column values, not just record IDs.
// Supabase: filtered realtime
const channel = supabase
.channel('task-updates')
.on('postgres_changes', {
event: '*',
schema: 'public',
table: 'tasks',
filter: `assignee_id=eq.${currentUserId}`,
}, (payload) => {
console.log(payload.new, payload.old);
})
.subscribe();
Supabase Realtime also supports Broadcast (ephemeral messages between clients) and Presence (online status tracking) — features PocketBase does not offer. For collaborative applications, this gap is meaningful.
File Storage
Both tools provide file storage, but with different backends.
PocketBase storage saves files to the local filesystem by default (pb_data/storage/). You can configure S3-compatible storage as the backend — this is recommended for production. Files are associated with collection records.
// PocketBase: attach file to record
const formData = new FormData();
formData.append('avatar', avatarFile);
formData.append('name', 'Alice');
const record = await pb.collection('users').create(formData);
// File URL: https://api.yourdomain.com/api/files/users/{recordId}/{filename}
Supabase Storage runs as a dedicated container (supabase/storage-api) backed by S3 or local filesystem, with image transformations via imgproxy. It adds access policies enforced by PostgreSQL RLS — your storage access rules live in the same policy system as your database.
// Supabase: storage with RLS policies
const { data, error } = await supabase.storage
.from('avatars')
.upload(`${userId}/profile.jpg`, avatarFile);
// Access controlled by storage policy:
// CREATE POLICY "Users can access own files"
// ON storage.objects FOR ALL
// USING (auth.uid()::text = (storage.foldername(name))[1]);
For multi-tenant applications where storage access must be tied to database-level permissions, Supabase's unified RLS approach is architecturally cleaner.
Row-Level Security
This is the sharpest divergence between the two systems from a data security standpoint.
PocketBase access rules are written in PocketBase's filter syntax inside the admin UI. You define rules per operation (List, View, Create, Update, Delete) per collection. Rules have access to the request context, the authenticated user's record, and related collections.
# PocketBase collection rule (filter expression)
# "Users can only see their own posts"
@request.auth.id = author
# "Users can see published posts or their own drafts"
published = true || @request.auth.id = author
# Admin bypass: empty string = allow all (for admin-only collections)
The syntax is readable and sufficient for most applications. Limitations: you cannot express complex multi-table security policies, and rules are evaluated at the API layer rather than at the database level — a direct database connection bypasses them.
Supabase RLS is enforced by PostgreSQL itself. Every query, from any connection, goes through the same policy check. This includes the REST API (PostgREST), edge functions, and direct database connections.
-- Supabase: PostgreSQL RLS
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
-- Users see their own posts + published posts from others
CREATE POLICY "read_posts"
ON posts FOR SELECT
USING (
auth.uid() = author_id
OR published = true
);
-- Only authors can update their own posts
CREATE POLICY "update_own_posts"
ON posts FOR UPDATE
USING (auth.uid() = author_id)
WITH CHECK (auth.uid() = author_id);
-- Multi-tenant: users can only see data from their organization
CREATE POLICY "org_isolation"
ON tasks FOR ALL
USING (
org_id IN (
SELECT org_id FROM memberships
WHERE user_id = auth.uid()
)
);
PostgreSQL RLS can express any policy that PostgreSQL can compute — including subqueries, joins, function calls, and row-level attribute comparisons. For applications with strict multi-tenancy requirements, complex role hierarchies, or compliance obligations, Supabase's native RLS is a significant advantage.
SDK Quality
JavaScript / TypeScript
Both SDKs are actively maintained and production-ready.
PocketBase JS SDK (pocketbase on npm) is a single-file client with no dependencies. Authentication, CRUD, file uploads, and real-time subscriptions are all first-class. Auto-cancellation handles component unmount in React/Vue.
import PocketBase from 'pocketbase';
const pb = new PocketBase('https://api.yourdomain.com');
// Type-safe with generated types
type Task = {
id: string;
title: string;
status: 'todo' | 'done';
assignee: string;
};
const tasks = await pb.collection('tasks').getList<Task>(1, 50, {
filter: 'status = "todo"',
expand: 'assignee',
});
Supabase JS SDK (@supabase/supabase-js) has more surface area — it wraps auth, PostgREST, realtime, storage, and edge functions. TypeScript support is excellent, and Supabase's CLI generates type definitions directly from your database schema.
import { createClient } from '@supabase/supabase-js';
import type { Database } from './database.types'; // generated by CLI
const supabase = createClient<Database>('https://db.yourdomain.com', 'anon-key');
// Fully typed: column names, filter operators, and return types are all inferred
const { data: tasks, error } = await supabase
.from('tasks')
.select('id, title, status, assignee(name, email)')
.eq('status', 'todo')
.order('created_at', { ascending: false });
Type Generation
PocketBase: pocketbase-typegen (community package) introspects the running PocketBase instance and generates TypeScript types from your collections.
npx pocketbase-typegen \
--url http://127.0.0.1:8090 \
--email admin@yourdomain.com \
--password yourpassword \
--out src/pocketbase-types.ts
Supabase: The official CLI generates types directly from your PostgreSQL schema — more reliable since it reads the actual database rather than an API.
supabase gen types typescript \
--project-id your-project-ref \
--schema public > src/database.types.ts
# Or for self-hosted
supabase gen types typescript \
--db-url postgres://postgres:password@localhost:5432/postgres \
--schema public > src/database.types.ts
Supabase's type generation is more mature — it handles complex PostgreSQL types (enums, custom types, stored procedure return types) that the PocketBase typegen cannot capture because PocketBase's type system is simpler.
Dart / Flutter SDK
Both maintain first-party Dart SDKs.
PocketBase Dart SDK is maintained by the PocketBase core team. Its API mirrors the JS SDK exactly — same method signatures, same real-time subscription model. For Flutter developers building mobile apps, PocketBase is often the default choice: single binary backend, Dart SDK, zero infrastructure.
Supabase Flutter SDK is maintained by the Supabase team and is production-grade — used by many Flutter apps in production. It provides the same full feature set as the JS SDK, including auth UI widgets via supabase_auth_ui.
Neither SDK has a meaningful quality gap for Dart/Flutter development. The deployment complexity of the backend is the deciding factor when choosing PocketBase vs Supabase for Flutter apps.
Production Operations
Backups
PocketBase backup is a single command:
#!/bin/bash
# backup-pocketbase.sh
DATE=$(date +%Y%m%d-%H%M%S)
BACKUP_DIR="/var/backups/pocketbase"
mkdir -p "$BACKUP_DIR"
# Stop PocketBase briefly for consistent backup (or use --automigrate with WAL)
systemctl stop pocketbase
tar -czf "$BACKUP_DIR/pb-backup-$DATE.tar.gz" /opt/pocketbase/pb_data/
systemctl start pocketbase
# Or: hot backup using SQLite's online backup API
sqlite3 /opt/pocketbase/pb_data/data.db ".backup $BACKUP_DIR/db-$DATE.sqlite"
# Push to S3
aws s3 cp "$BACKUP_DIR/pb-backup-$DATE.tar.gz" s3://backups/pocketbase/
# Cleanup local > 7 days
find "$BACKUP_DIR" -name "*.tar.gz" -mtime +7 -delete
Restore: stop the service, replace pb_data/, start. No database client required.
Supabase backup requires coordinating multiple data stores:
#!/bin/bash
# Backup PostgreSQL
docker exec supabase-db-1 \
pg_dump -U postgres postgres | gzip > "postgres-$(date +%Y%m%d).sql.gz"
# Backup storage files (if using local filesystem)
tar -czf "storage-$(date +%Y%m%d).tar.gz" /path/to/supabase/volumes/storage
# Push both to S3
aws s3 cp postgres-$(date +%Y%m%d).sql.gz s3://backups/supabase/
aws s3 cp storage-$(date +%Y%m%d).tar.gz s3://backups/supabase/
Restore is more involved: bring up the PostgreSQL container, restore the dump, bring up remaining services. Storage files must match the database state, making point-in-time recovery more complex.
Updates
PocketBase updates are a binary swap:
# Update PocketBase
systemctl stop pocketbase
wget https://github.com/pocketbase/pocketbase/releases/download/v0.26.0/pocketbase_linux_amd64.zip
unzip -o pocketbase_linux_amd64.zip
systemctl start pocketbase
# Migrations run automatically on startup
PocketBase has a strong commitment to backward compatibility. Major version upgrades have migration guides; minor/patch updates are typically safe binary drops.
Supabase updates require updating image tags and checking breaking changes across 7+ components:
# Review changelog first
# Update image tags in docker-compose.yml, then:
docker compose pull
docker compose up -d
# Run pending database migrations
docker exec supabase-db-1 psql -U postgres -f /docker-entrypoint-initdb.d/migrations.sql
Supabase's self-hosted update cadence has historically lagged the cloud version by 1–3 months for major features. Some releases require manual migration steps between specific version pairs.
Monitoring
PocketBase exposes /api/health and logs structured output to stdout. For monitoring, standard tools (Uptime Kuma, Prometheus with the process exporter, or a hosted APM) connect directly to the binary.
Supabase's analytics container (Logflare) provides built-in log aggregation. For production monitoring, docker stats, Prometheus with cAdvisor, or a hosted service like Grafana Cloud is recommended. The Supabase Studio dashboard (if deployed) shows real-time database stats, query performance, and API usage.
Cost Comparison: Self-Hosted
| Scale | PocketBase VPS | Supabase Self-Hosted VPS | Supabase Cloud |
|---|---|---|---|
| Dev / side project | $4–6/month (512 MB) | $24/month (4 GB) | $0 (free tier) |
| 1K–10K MAU | $6/month | $24–30/month | $25/month (Pro) |
| 10K–100K MAU | $12–20/month | $40–60/month | $25–$200/month |
| 100K+ MAU | $40–80/month | $80–150/month | $200–$500+/month |
For side projects and MVPs, Supabase Cloud's free tier is actually the cheapest option — you get a managed PostgreSQL backend with no ops overhead and no server cost. Self-hosting Supabase saves money at the $25+/month tier when you are running multiple projects (Supabase Cloud charges per project) or need data sovereignty.
PocketBase self-hosted is cheapest across all scales for single-server deployments, by a significant margin.
Decision Framework
Choose PocketBase self-hosted if:
- Your application fits on a single server — most apps do, including apps with 50K+ users
- You're building a Flutter/mobile app and want the simplest possible backend
- Deployment complexity must be minimal — you want to
scpone file to a server and be done - SQLite's operational simplicity outweighs PostgreSQL's feature set for your use case
- You want Go-level hooks for custom business logic embedded in the binary
- Your budget for infrastructure is $4–12/month
- You're building a starter kit or boilerplate — see T3 Stack vs Next.js SaaS Starters 2026 for PocketBase and Supabase-based starters
Choose Supabase self-hosted if:
- PostgreSQL is a hard requirement — complex queries, RLS, PostGIS, pgvector, or extensions
- You need horizontal scaling or read replicas (Supabase supports PostgreSQL replication; PocketBase does not)
- Multi-tenant SaaS with strict row-level data isolation requirements
- Your team already knows PostgreSQL and prefers SQL over collection-level filter syntax
- Compliance requirements mandate that audit logs, session data, and application data live in the same database with row-level access controls
- You need phone OTP authentication or enterprise SAML SSO
- You have the infrastructure budget and ops capacity for a 7-container stack
- You're replacing your own self-hosted storage stack and want a backend that handles multi-user data access with the same RLS model
Consider PocketBase Cloud (PocketHost) if:
- You want PocketBase's simplicity without server management — PocketHost offers hosted PocketBase with automatic SSL and backups
Consider Supabase Cloud if:
- Your project is in early stages and you want zero ops overhead — the free tier covers development and early production
- You need database branching (preview environments per PR) — Supabase Cloud supports this, self-hosted does not yet
Related Guides
- Self-Host Nextcloud on Docker 2026 — complete self-hosted cloud storage stack with Traefik, SSL, and backups
- Self-Host Mattermost: Slack Alternative 2026 — adding team messaging to a self-hosted stack
Related: PocketBase vs Supabase 2026 · Appwrite vs PocketBase 2026 · Self-Hosting Guide: Supabase 2026