6.2 KiB
Server Infrastructure Migration: Ditching Supabase (Execution Status)
Status: Active Migration / Implementation Target Architecture: Self-hosted Postgres, Prisma ORM, Zitadel (Auth/Tenancy), S3-compatible Storage (MinIO or VFS) Prior Reference:
ditch-supabase.md(initial research)
This document tracks concrete migration status and remaining work to remove Supabase dependencies from server infrastructure.
1) Database & ORM: Prisma
While Drizzle was initially highly considered, the current CLI implementation (pm-cli-cms) successfully validated Prisma (with @prisma/adapter-pg and pg.Pool) as our query layer of choice.
Why Prisma?
- Type safety and schema synchronization is best-in-class.
- The
schema.prismafile serves as the definitive source of truth for the database layout. - The CLI testbed proved we can dynamically spin up connections to different tenant databases.
Status / Next steps:
- Schema Centralization: Establish a core
schema.prismarepresenting the standard tenant database. - Server-Side Integration: Replace the ~27 server files currently making PostgREST
supabase.from()calls with Prisma queries (prisma.posts.findMany(), etc.). - Connection Pooling: Implement a caching/pooling mechanism in the server backend to hold active Prisma clients for each tenant (since Zitadel manages tenancy per-database).
2) Authentication & Tenancy: Zitadel
The architecture has evolved beyond simple email/password auth. We are utilizing Zitadel as our central Identity Provider (IdP). Zitadel is explicitly designed to handle B2B tenancy.
Auth chapter (flows, payloads, host setup, references): docs/supabase/auth-zitadel.md
The Multi-Tenant Database Model: Instead of relying on Supabase Row-Level Security (RLS) to isolate tenant data inside a massive single database, we are opting for database-per-tenant (or schema-per-tenant) isolation mapped by Zitadel.
Status / Next steps:
- API Middleware: Replace the Supabase JWT verifiers in Hono with Zitadel token verification.
- Instance Provisioning: When Zitadel registers a new application/tenant, our backend needs a Webhook/Event trigger to dynamically fire the
pm-cli-cms site-initor equivalent workflow to provision an isolated Postgres database. - Database Routing: The server backend must extract the Tenant ID from the Zitadel JWT on incoming requests and fetch/instantiate the Prisma client bound to that specific tenant's
DATABASE_URL.
3) Storage Migration Strategy (VFS & Mounts)
We are fully moving away from Supabase Storage (Buckets). The replacement strategy relies heavily on our mature, battle-tested Virtual File System (VFS) located at server/src/products/storage/api/vfs.ts.
Why the VFS replaces Supabase Buckets:
- Native File Abstraction: The VFS supports flexible storage mounts. We can seamlessly swap out the underlying physical backend to an S3-compatible generic provider (like MinIO) or local disk without rewriting any of our API handlers.
- Built-in ACL Integration: The VFS natively integrates with
@polymech/aclfor granular permission checks (Read/Write/Delete) against file paths, meaning we no longer need Supabase bucket policies.
Current implemented tooling:
pm-cli-cms backup-store- can export Supabase bucket files directly into local VFS images root:
pm-cli-cms backup-store --source ./server --target ./server/storage/images- preserves
cache/...paths and emits picture UUID aliases understorage/<user_id>/<picture_id>.<ext>.
pm-cli-cms migrate-images-vfs- rewrites
pictures.image_urlto VFS URLs: <IMAGE_VFS_URL>/api/vfs/get/<IMAGE_VFS_STORE>/<user_id>/<picture_id>[.<ext>]- supports direct Supabase URLs and nested
/api/images/render?url=...wrappers. - resolves missing local files via hash matching and optional hydration from old URL content.
- rewrites
- Server-side image compatibility
server/src/products/images/index.tscontains compatibility handlers for legacy thumbnail/render URL patterns during migration.
4) Authorization & Security (ACL Strategy)
The major milestone: RLS is deprecated.
We no longer rely on Supabase Row-Level Security (auth.uid()) at the database level.
Application-Layer Enforcement via @polymech/acl:
- Our own ACL engine correctly evaluates paths, nested inheritance, and group-based hierarchies via
acl_groupsandresource_acl. - Because Zitadel gives us Tenant IDs and specific roles up-front in the JWT middleware, the application layer resolves exactly what the connection session is permitted to access.
- Done: The core logic required to isolate access is fully complete inside the ACL module. It's strictly a matter of stripping the old RLS policies from the Postgres schemas entirely (treating the database as fully trusted by the API backend).
5) Execution Pipeline
Milestone 1: Data Migration Engine (Completed)
- ✅ Validated raw self-hosted Postgres (
polymech.info:5432) - ✅
pg_dumpandpsqlwrapping via our CLI (pm-cli-cms backup-site/db-import). - ✅ Stripping out Supabase-specific system schemas (
auth,realtime,storage) from exported backups to ensure hygienic vanilla Postgres DBs.
Milestone 2: Server ORM Cut-Over (In Progress)
- 🚧 Bootstrap
prisma/schema.prismamatching the clean vanilla Postgres schema. - 🚧 Systematically replace
supabase.from()with ORM query paths in server modules. - ✅ Added Drizzle query CLI baseline:
pm-cli-cms db-query-drizzlewith query/transaction timing metrics and report output.
Milestone 3: Zitadel & Routing
- 🚧 Replace
@polymech/acl's dependency onauth.uid()or Supabase cookies with Zitadel JWT payload assertions. - 🚧 Implement the dynamic database connection router to select the right Prisma instance based on the Zitadel Tenant ID.
Milestone 4: Storage Migration Cut-Over (In Progress)
- ✅ VFS-backed image upload path implemented (
/api/images?forward=vfs). - ✅ CLI workflows added for file backup + URL rewrite (
backup-store,migrate-images-vfs). - 🚧 Complete migration runbooks + verification reports in production.
- 🚧 Remove temporary legacy URL compatibility code after migration is complete.