How to Migrate a Supabase App (Postgres, Auth, Edge Functions) to Kubernetes on Your Own Cloud Account
A step-by-step guide to moving a Supabase app off the hosted platform: dump and restore Postgres (including auth.users), run GoTrue or import your bcrypt hashes into an IdP without password resets, and turn Deno Edge Functions into containers on Kubernetes in your own AWS, GCP, Azure, or Scaleway account.
A Supabase migration is three migrations in one. You move Postgres (including the auth and storage schemas), the Auth service (GoTrue) with its JWT signing key, and the Deno Edge Functions. Run them as three separate cutovers with three rollback plans, never in the same window.
Postgres moves with a dump and restore or with logical replication. A dump and restore needs a planned write freeze of roughly 15 to 60 minutes for most apps. Logical replication gets the cutover down to seconds. Restore into managed Postgres (Amazon RDS or Aurora, Cloud SQL, Azure Database for PostgreSQL Flexible Server, Scaleway Managed Database), not a StatefulSet.
Nobody has to reset their password. Supabase Auth is the open-source GoTrue service, and the bcrypt hashes in auth.users.encrypted_password are ordinary table data. You can run supabase/auth yourself, or bulk-import the hashes into Auth0, Clerk, Amazon Cognito or SuperTokens.
Edge Functions do not run on Kubernetes unchanged. Each Deno function becomes a route in your API, a small Deno container with its own Deployment, or a serverless function. In the client, supabase.functions.invoke() becomes a plain fetch.
If you drop PostgREST, every supabase-js.from().select() call in your frontend breaks. Run PostgREST as a container to keep the client code, or budget for an API rewrite before you touch the database.
Qovery deploys the migrated stack into your own AWS, GCP, Azure or Scaleway account, or your existing Kubernetes cluster. That covers your API, GoTrue, each former Edge Function and per-PR preview environments, with the database on a managed cloud service. The bill, the VPC and the data residency stay in your name, and you don't need to hire a DevOps engineer.
A Supabase project looks like one product behind one URL. Under the hood it runs at least six open-source services: Postgres, PostgREST, GoTrue, Realtime, Storage and an API gateway, plus a Deno runtime for Edge Functions. That changes how you plan the migration: you are moving a handful of well-known open-source components, and each one can move on its own schedule. Moving all of it in one weekend is how migrations end up rolled back, so this guide breaks the job into the pieces that actually move.
What exactly are you migrating when you leave Supabase?
A Supabase project is a set of open-source services behind one URL: Postgres, PostgREST, GoTrue (Auth), Realtime, Storage and a gateway, plus Edge Functions on supabase/edge-runtime. Because each piece is a separate project, you can move them one at a time instead of in one big-bang cutover.
PostgREST container on Kubernetes, or fold into your API
None if kept, rewrite if dropped
Auth
supabase/auth (GoTrue)
GoTrue container, or an IdP
Config only with GoTrue, rewrite with an IdP
Realtime
supabase/realtime (Elixir)
Container, or your own WebSocket/pub-sub
Config only if kept
Storage
supabase/storage-api
S3, GCS, Azure Blob or Scaleway Object Storage behind your API
Rewrite of upload/download calls
Edge Functions
supabase/edge-runtime (Deno)
API routes, Deno containers or serverless
functions.invoke() becomes fetch
Gateway
Envoy (Kong in older setups)
Your ingress controller or cloud load balancer
None
The usual reasons to leave are about ownership: data residency and compliance (GDPR, HIPAA, regional rules), cost at scale, one consolidated cloud bill with your own discounts, VPC-private networking, or a company-wide Kubernetes standard.
Run this inventory before you plan anything. It takes five minutes:
SQL|Users and auth footprint
SELECT count(*) AS users FROM auth.users;
SELECT provider, count(*) FROM auth.identities GROUP BY provider;
-- Extensions you depend on
SELECT extname, extversion FROM pg_extension ORDER BY extname;
-- RLS policies and the tables they protect
SELECT schemaname, tablename, policyname, cmd FROM pg_policies ORDER BY 1, 2;
-- Storage footprint per bucket
SELECT bucket_id, count(*) AS objects,
pg_size_pretty(sum((metadata->>'size')::bigint)) AS size
FROM storage.objects GROUP BY bucket_id;
Also count your Edge Functions, OAuth providers, cron jobs and database triggers. For effort: a small app takes a weekend. An app with deep RLS, Storage and a dozen functions takes several weeks of calendar time across three cutovers.
How do you migrate Supabase Postgres to your own cloud account without downtime?
Use a dump and restore when you can afford a short planned write freeze (15 to 60 minutes for most apps). When you can't, use Postgres logical replication or a managed migration service such as AWS DMS or Google's Database Migration Service. Either way, restore into a managed Postgres service rather than a pod.
Before you touch the database, answer the PostgREST question, because it decides how much of your frontend changes. supabase-js calls like .from('orders').select() go to PostgREST, not to Postgres directly. If you keep PostgREST as a container pointed at the new database, the client code stays the same. If you drop it, you're rewriting those calls against your own API.
The dump and restore path
Supabase documents a three-file dump with its CLI, which wraps pg_dump: roles, then schema, then data (Supabase backup and restore guide):
BASH
supabase db dump --db-url "$SUPABASE_DB_URL" -f roles.sql --role-only
supabase db dump --db-url "$SUPABASE_DB_URL" -f schema.sql
supabase db dump --db-url "$SUPABASE_DB_URL" -f data.sql --use-copy --data-only \
-x "storage.buckets_vectors" -x "storage.vector_indexes"
The restore runs in one transaction and stops on the first error. It sets session_replication_role = replica so triggers don't fire twice during the data load:
The same guide flags what the dump leaves out. Storage files are not in the database dump, custom triggers and policies on the auth and storage schemas need a separate diff, and the Vault root key isn't in any backup. On a non-Supabase target, expect to edit supabase_admin ownership lines. Also check that your managed service's admin role is allowed to set session_replication_role.
Check your extensions before you pick a target
pg_cron, pgvector and PostGIS are on the supported lists of Amazon RDS, Cloud SQL and Azure Flexible Server. pgsodium, supabase_vault, pg_graphql, pg_net and pgjwt are not on any of the three lists when I checked. Plan application-side replacements for those: KMS or your secrets manager instead of Vault, an HTTP call from your API instead of pg_net. Re-check the lists on the day you migrate, because they change.
Near-zero downtime with logical replication
After restoring roles and schema on the target, publish on Supabase and subscribe from the target:
SQL|On Supabase
CREATE PUBLICATION app_migration FOR TABLES IN SCHEMA public, auth;
-- On the target
CREATE SUBSCRIPTION app_migration
CONNECTION 'host=db.<ref>.supabase.co port=5432 dbname=postgres user=postgres password=...'
PUBLICATION app_migration;
-- On Supabase, watch lag until it is near zero
SELECT application_name, state, replay_lag FROM pg_stat_replication;
Logical replication has gaps you must cover by hand. DDL and sequence values are not replicated, large objects are not replicated "and there is no workaround", and tables need a primary key or replica identity for updates and deletes (PostgreSQL docs). TRUNCATE does replicate, but it fails if foreign keys point at tables outside the subscription.
Cutover runbook: freeze writes, compare row counts per table, bump every sequence with setval to the source's latest value, set passwords for roles that have LOGIN, switch DATABASE_URL, then drop the subscription. Keep the Supabase project read-only for 7 days as your rollback path.
Method
Write freeze
Not carried over
Extensions
Rollback
Pick it when
supabase db dump + psql
15-60 min for most apps
Storage files, Vault key, some auth/storage customizations
Enable on target before restore
Point back at Supabase
Small to mid-size DBs, freeze acceptable
Logical replication
Seconds at cutover
Sequences, DDL, large objects
Enable on target before subscribing
Reverse publication or point back
Larger DBs, no freeze window
AWS DMS / Google DMS
Seconds to minutes
Check each service's Postgres source limitations
Manual on target
Point back at Supabase
You want a managed console and monitoring
On why managed Postgres beats a StatefulSet: RDS keeps automated backups for up to 35 days with point-in-time restore (AWS docs). Azure Flexible Server keeps 7 to 35 days (Microsoft Learn). Cloud SQL Enterprise Plus keeps PITR logs for up to 35 days, and Enterprise for up to 7 (Google Cloud docs). CloudNativePG is a good operator, and it entered the CNCF as a Sandbox project in January 2025 (CNCF). Only take that route if someone on the team actually does DBA work.
How do you migrate Supabase Auth without forcing every user to reset their password?
You can keep every existing login working. Supabase Auth is the open-source GoTrue service, and the bcrypt hashes in auth.users.encrypted_password move over as ordinary table data, so neither path needs a reset email.
Option A: run GoTrue yourself. Deploy the supabase/auth image next to your API. The minimum configuration, taken from the supabase/auth README and the official compose file:
BASH
GOTRUE_DB_DRIVER=postgres
GOTRUE_DB_DATABASE_URL=postgres://supabase_auth_admin:...@your-db:5432/postgres
GOTRUE_JWT_SECRET=... # same secret as the Supabase project
GOTRUE_SITE_URL=https://app.example.com
API_EXTERNAL_URL=https://auth.example.com
GOTRUE_SMTP_HOST=... GOTRUE_SMTP_PORT=... GOTRUE_SMTP_ADMIN_EMAIL=...
Check which signing key your project uses first. Supabase now recommends asymmetric keys (ES256 or RS256), and the legacy shared JWT secret "is the only key that can be extracted from Supabase" (Supabase signing keys docs). With the legacy secret, existing access tokens keep validating on your GoTrue. With asymmetric keys, you have to issue new keys, so access tokens signed by Supabase stop validating. Plan for users to refresh or sign in once.
Option B: move to an identity provider. Bcrypt import support differs by vendor:
Target
Bcrypt hash import
auth.uid() / RLS compatibility
Where it runs
Pricing model
Self-hosted GoTrue
Native, same table
Unchanged
Your cluster
Your compute only
Keycloak
No built-in bcrypt: ships Argon2 and PBKDF2 (Keycloak docs), needs a custom hash provider
Whichever issuer you choose, protect your RLS policies. auth.uid() reads the sub claim that PostgREST puts into the request settings. Before you switch issuers, list every claim your policies read (sub, role, email, app_metadata). If a new token lacks one, the policy doesn't error. It denies access without telling you.
Then re-register every OAuth provider's redirect URL against the new auth domain, and re-provision SMTP, email templates and magic links. Refresh tokens live in auth.refresh_tokens. Copy them over and users stay logged in. Drop them and everyone signs in once. Pick one and say so in your release notes. Before the DNS cutover, test sign-in with a sample of real accounts against the new service.
Ship faster on infrastructure you control.
Qovery gives your team self-service deployments on your own AWS, GCP, Azure, or Scaleway account - or your existing Kubernetes cluster. Start deploying in under 10 minutes.
What replaces Supabase Edge Functions on Kubernetes?
Edge Functions are Deno scripts that run on supabase/edge-runtime, so Kubernetes can't run them as they are. Each function becomes a route in your existing API, its own Deno container with a Deployment and Service, or a serverless function if you need scale-to-zero.
Moving also lifts the hosted limits. Supabase caps each function at 256 MB of memory, 2 s of CPU time per request and 400 s of wall clock on paid plans (Supabase docs). In your own container, the only ceilings are the resource requests you set.
Target
Code changes
Cold start
Scale to zero
Execution ceiling
Ops burden
Fold into your API
Port to your backend language or runtime
None
No
Your API timeouts
Lowest
Deno container on Kubernetes
Minimal: imports, env, client
None (always-on)
No by default
Your pod limits
One Deployment per function
AWS Lambda / Cloud Run / Azure Container Apps
Wrap handler or containerize
Yes
Yes
Provider limits
Low, but one more platform
Code changes are smaller than they look. Deno.serve keeps working, and Deno.env.get() reads ordinary container env vars. Review npm: and jsr: imports, and replace the service-role supabase-js client with a real Postgres role and internal service auth. Function secrets become Kubernetes Secrets.
DOCKERFILE
FROM denoland/deno:latest
WORKDIR /app
COPY . .
RUN deno cache main.ts
EXPOSE 8000
CMD ["run", "--allow-net", "--allow-env", "main.ts"]
Pin the image tag in production. The Deployment needs a readiness probe on a cheap /health route, which is the line people forget. Without it, Kubernetes routes traffic to the pod before Deno is listening. In the client, replace supabase.functions.invoke('name') with a fetch to your own domain behind one constant, and keep both endpoints live for a week so rollback is a one-line change. Scheduled functions become Kubernetes CronJobs, or stay in pg_cron, which RDS, Cloud SQL and Azure all support.
One latency caveat: Supabase runs functions close to users in many regions, so a single-region cluster adds round-trip time for distant users. Put a CDN in front before you consider multi-region.
What does Supabase cost versus running the same stack on your own cloud account?
Self-hosting rarely wins for small projects. A Kubernetes control plane, managed Postgres and engineer time already cost more than the Supabase Pro base price. It starts to win once compute, storage and egress are big enough that your own cloud discounts apply and you can stop non-production environments outside working hours.
Supabase Pro starts at $25/month and Team at $599/month. Both include $10 of compute credits, 8 GB of disk per project, 250 GB of egress and 100 GB of file storage. Overages are $0.125/GB for disk and $0.09/GB for egress. The Large compute add-on (2 dedicated vCPUs, 8 GB RAM) is $110/month (Supabase pricing).
On your own account, the fixed floor is the cluster: $0.10 per cluster per hour on EKS standard support (AWS) and on GKE (Google Cloud), while AKS has a free tier without an SLA (Azure). Add the engineer-days for upgrades, restore drills, on-call and observability. The upside is discounts that only apply in your own account: up to 66% with Compute Savings Plans and 72% with EC2 Instance Savings Plans (AWS).
Your cloud's rates; AWS includes 100 GB/month free (AWS)
Who runs upgrades
Supabase
You
Qovery runs cluster upgrades, cloud provider patches the DB
Savings Plans / CUDs
No
Yes
Yes
Whose account the bill lands in
Supabase's
Yours
Yours
Should you self-host the full Supabase stack or rebuild on standard cloud services?
If you self-host all of Supabase, supabase-js keeps working everywhere, but you now operate every service yourself on a community-supported distribution. If you decompose onto managed Postgres and your own API, the lock-in goes away, but your frontend changes. The deciding factor is almost always how heavily you use PostgREST and RLS.
Supabase is clear about the self-hosted trade-off. Self-hosted Supabase "is community-supported". It has no branching, managed backups, PITR or Management API, and you are responsible for "high availability and scalability" and "backups and disaster recovery" (Supabase self-hosting docs). The Kubernetes Helm charts are community-maintained.
Path A: self-host Supabase
Path B: decompose
Path C: hybrid, Postgres first
Frontend changes
None
Data, auth, storage calls
Gradual
Services you operate
Postgres, PostgREST, GoTrue, Realtime, Storage, gateway, edge-runtime, Studio
Your API, GoTrue or IdP
Grows per phase
Upgrade burden
High, every component
Low, mostly managed
Low to medium
Parity with hosted
Missing branching, managed PITR, Management API
Not applicable
Full until the final phase
Rollback difficulty
Medium
High once frontend ships
Low per phase
Typical duration
Days
Weeks
Weeks, low risk
With a live customer base, I'd pick Path C. Leave Auth and Storage on Supabase at first, move Postgres (with PostgREST in front of it), then the functions, then Auth last. The catch is foreign keys to auth.users: keep replicating the auth schema from Supabase into the new database until Auth moves. Never run the Postgres, Auth and Functions cutovers in the same window.
For Postgres alone, Neon, RDS/Aurora and Cloud SQL are all fair options. For the application side, teams usually compare Render, Northflank, Porter and Coolify.
How does Qovery handle the Kubernetes side of a Supabase migration?
Qovery creates and runs the Kubernetes cluster and deploys every piece of the migrated stack into your own AWS, GCP, Azure or Scaleway account, or into your existing Kubernetes cluster. A small team can finish the migration without hiring a DevOps engineer.
Your API, the GoTrue container, PostgREST if you keep it, and each former Edge Function become Qovery services that deploy on git push. Each environment gets its own env vars and secrets, which replace Supabase function secrets. Databases run on managed cloud services (Amazon RDS, Cloud SQL, Azure Database for PostgreSQL, Scaleway Managed Database), so your Postgres keeps managed backups and failover.
Two features matter most for a multi-week migration. Preview environments give every pull request a full copy of the stack, so you can test the auth flow and rewritten functions before the cutover. Environment auto-stop shuts those copies down outside working hours. Qovery also runs the cluster upgrades and provides per-environment RBAC, the operational work that lands on your team the day you leave a hosted platform. The cloud bill, your committed-use discounts, the VPC and data residency all stay in your name, which is usually why the team left in the first place.
If you're a solo developer on a side project, stay on Supabase. It's a good product, and the migration isn't worth your weekend. This path is for teams that need the data and the bill in their own account.
How do I run this migration with Claude Code or Cursor?
Install the Qovery Agent Skills once. Your coding agent can then connect your cloud account, deploy the migrated services and create preview environments from plain-language instructions, and every action waits for your approval.
curl -fsSL https://skill.qovery.com/install.sh | bash
# Same install, scoped to the current repository
curl -fsSL https://skill.qovery.com/install.sh | bash -s -- --project
# Connect Claude Code to the Qovery MCP Server (OAuth, read-only by default)
claude mcp add --transport http qovery https://mcp.qovery.com/mcp --callback-port 4242
# Write access, only when you want the agent to change infrastructure (also enable it in the Qovery Console)
claude mcp add --transport http qovery "https://mcp.qovery.com/mcp?read_write=true" --callback-port 4242
During the migration, qovery-onboard connects the cloud account and cluster, and qovery-deploy ships the API, GoTrue and function containers. qovery-preview creates per-PR copies to test auth and function rewrites, and qovery-troubleshoot debugs failing deployments. qovery-optimize, qovery-speedup and qovery-terraform cover the rest. Then ask something like "deploy this Deno function as a service on my GCP account with Qovery and wire it to the migrated Postgres". Setup details are in the AI agent quickstart and on the Claude Code integration page.
Frequently asked questions
Can I migrate Supabase Auth users without making them reset their passwords?
Yes. The bcrypt hashes in auth.users.encrypted_password move over with the rest of the data, so self-hosted GoTrue accepts existing passwords as they are. Auth0, Clerk, Amazon Cognito and SuperTokens all document bcrypt hash import. Keycloak needs a custom hash provider because it ships only Argon2 and PBKDF2.
Can Supabase Edge Functions run on Kubernetes unchanged?
No. They run on supabase/edge-runtime, so each function needs a small Deno container, a route in your API or a serverless target. The code changes are usually small: imports, env vars and replacing the service-role client.
Should I run Postgres inside Kubernetes or use a managed database after leaving Supabase?
Use a managed database unless someone on your team does real DBA work. RDS, Azure Flexible Server and Cloud SQL Enterprise Plus all offer up to 35 days of point-in-time recovery out of the box. CloudNativePG is a solid operator, but backups, failover and upgrades become your job.
How long does a Supabase to Kubernetes migration take, and how much downtime should I plan?
A small app takes a weekend. An app with deep RLS, Storage and a dozen functions takes several weeks across three cutovers. Plan a 15 to 60 minute write freeze for a dump and restore, or seconds of downtime with logical replication.
Is self-hosting Supabase on Kubernetes cheaper than Supabase Pro?
Not for small projects. Supabase Pro starts at $25/month, while a cluster, managed Postgres and the engineer time to run them cost more than that before any traffic. Self-hosting pays off at scale, when your own Savings Plans or committed-use discounts and auto-stopped staging environments apply.
Do I still need supabase-js and PostgREST after migrating off Supabase?
Only if you want your frontend to stay the same. Keep PostgREST as a container and supabase-js data calls keep working against your new database. Drop it, and every .from().select() call has to move to your own API.
Romaric founded Qovery to make Kubernetes accessible to every engineering team. He writes about platform strategy, developer experience, and the future of cloud infrastructure.
Next step
Ship faster on infrastructure you control.
Qovery gives your team self-service deployments on your own AWS, GCP, Azure, or Scaleway account - or your existing Kubernetes cluster. Start deploying in under 10 minutes.