Read the schema as access patterns
For each Postgres table, list how the application actually queries it. Mark joins and ad-hoc filters explicitly — they decide what reshapes, what gets a search index, and what stays on Postgres.
Declare tables, buckets, and workers
Write the stack.json project: tables with the keys your access patterns need, buckets for Storage, nodejs workers for Edge Functions, and a vector index if pgvector is in use. Review the plan diff.
Export, reshape, load, verify
Export tables with pg_dump or CSV, reshape rows to the declared keys with a script you keep, and load. Copy Storage files into buckets. Compare row counts and sample records against Supabase.
Cut over accounts and traffic
Switch the application to the KERNSTACK backend behind a password-reset or re-login flow for existing users, since password hashes cannot move. Retire the Supabase project when reads reach zero.
The one decision everything else depends on: your queries
KERNSTACK tables are key-value tables. There is no SQL, no joins, and no query planner underneath. So the first migration task is reading your Postgres schema as access patterns: which queries fetch by primary key or a known secondary key, and which genuinely join, aggregate, or filter ad hoc? The first group moves cleanly — each becomes a keyed read. The second group is a decision: denormalise into the key shape, answer it with a declared text or vector search index, or keep that dataset on Postgres and call it over the network.
Teams that skip this reading exercise and try to copy table shapes one-to-one end up with keys nobody queries by. Teams that do it usually find most product data is keyed already — users by id, orders by user and date, documents by owner — and the relational remainder is smaller than feared.
- Supabase Auth users are the sensitive piece: password hashes do not export. Plan a re-login or password-reset flow at cutover.
- Row Level Security policies do not translate. Access rules are enforced in your functions and workers, in code your reviewers read.
- Realtime subscriptions have no direct equivalent. If live updates are core to the product, weigh that before moving the datasets behind them.
What moves, in dependency order
Files first, because they are risk-free: Storage objects copy into buckets over HTTPS and nothing about the database changes. Data second: export each Postgres table with pg_dump or CSV export, reshape rows to the declared keys, load, and verify counts and spot-check records against Supabase. Code third: Edge Functions are TypeScript, and TypeScript becomes container workers with the nodejs runtime — expect small adjustments where Deno-specific APIs were used.
Cut users over last, alongside the account plan. The old Supabase project keeps running through the whole sequence, so every step can be checked against production behaviour before anyone depends on it.
Resource-by-Resource Mapping from Supabase
- Postgres tables (keyed access) →
tables: Rows export and load under an explicit key — typically the primary key your queries already use. Storage is 0.04 credits per GB-hour. - Postgres tables (joins, ad-hoc SQL) →
search indexes — or stay external: Full-text lookups become a declared text index; anything truly relational keeps a Postgres you call over the network. There is no hidden SQL layer. - pgvector columns →
vector-search indexes: Declare a vector index over the table that holds the source records. Embeddings can come from the catalogue model text-embed-v1 at 3 credits per 1,000 tokens. - Storage buckets →
buckets: Objects copy over HTTPS or the S3-compatible endpoint. Browsers upload with presigned URLs issued by your workers. - Edge Functions (Deno, TypeScript) →
workers (nodejs): TypeScript logic becomes container workers. Deno-only APIs need small rewrites; request and response logic usually carries over directly. - Auth (users and passwords) →
gateway sign-in flow: User records move; password hashes do not. Plan a reset or re-login at cutover and communicate it like a product change, because it is one. - Row Level Security policies →
checks in functions and workers: Policies are re-expressed as code in the handlers that touch the data — reviewable in the same diff as the rest of the change. - Realtime subscriptions →
no direct equivalent: Named plainly: if the product leans on live database subscriptions, keep those datasets on Supabase or redesign the update path before migrating them.
Frequently asked questions
With pg_dump or per-table CSV and JSON exports, a small reshape script that turns rows into keyed records, and a load into the declared tables. Keep the script — you will re-run it during rehearsal. Verify with row counts plus sampled record comparisons, not with hope.
Yes. Datasets that are genuinely relational or depend on realtime can stay on Supabase while compute, files, and workflows move to KERNSTACK; the two call each other over HTTPS. Partial moves are the norm, and the honest boundary is your query shapes, not a sales target.
You get key-value tables shaped like relational ones: keys nobody reads by, and application code simulating joins with fan-out reads. The review step is the migration. Budget the afternoon for it and the rest is export, load, and verification mechanics.
Related capabilities, architectures, and comparisons
Ship your stack on the Software Lifecycle Cloud.
Closed preview on dedicated European infrastructure. Bring a repository or start from a blueprint.