Skip to content

Scaling a Lovable app past the Supabase free tier

9 min readLovableSupabasePostgresMigrationDatabases

The default Lovable stack (Lovable plus Supabase) is genuinely good for the first version of an app. Single signup, generous free tier, auth and database and storage bundled. The problems show up later, when your app has real users and the bill or the architecture starts pushing back.

This post is about that "later." What actually breaks, and what to do about it without rewriting your app.

Contents

Where the default stack breaks

The free tier itself is rarely the problem. Most apps that hit the free-tier ceiling are happy to pay. The breaks are usually structural.

The pricing cliff. Supabase free is two projects, 500MB database, 5GB bandwidth. The Pro plan is $25 per month and covers most growing apps. But certain dimensions (database compute add-ons, larger storage, point-in-time recovery, longer log retention) are priced per dimension on top of the base. The bill can get to $150 per month surprisingly fast for an app with modest traffic but a database that grew.

Single engine. Supabase is Postgres-only. If your app needs anything else (Redis for sessions, vector search at scale, ClickHouse for analytics, MongoDB for a third-party integration) you are now multi-vendor. That is fine, it just changes the operational story.

Auth coupled to the database. Supabase auth lives in your Supabase Postgres. The auth.users table is in the same instance as your application tables, and row-level security policies often reference it. Moving the database without moving the auth is harder than it looks because the foreign keys and RLS policies expect both to be present.

Connection limits. Supabase Pro caps pooled connections around 200 per project. A growing app with serverless functions can hit this in a single bad afternoon. The fix exists (more compute, transaction-mode pooling) but it is a fix you have to think about, and Lovable does not warn you about it in advance.

None of these are dealbreakers on their own. The decision to move usually comes when two of them happen at once.

Three paths off

There are three reasonable moves.

Stay on Supabase, pay for the upgrades. If you like the bundled developer experience and your app fits within Supabase's shape, this is the cheapest path measured in your time. Pay the $25 base, add the dimensions you need, do not migrate. The cost is a yearly $300 to $1800 depending on growth, but you keep your weekends.

Move the database, keep Supabase for auth. This is what most teams actually do. The Postgres goes to a database vendor (Layerbase, Neon, your own host). Supabase becomes auth-only. The free tier for auth alone is generous, your bill drops, and you keep the auth UX you already wired up. This is the lowest-risk move because it changes one thing at a time.

Move everything off Supabase. Database, auth, and storage all leave. This used to mean re-homing auth on Clerk or Better-Auth and forcing every user to reset their password, which is why most teams avoided it. That part changed: migrating a Supabase project to Layerbase brings the auth.users table across with the password hashes intact, so you skip the mass reset. Storage (the actual files) is still a separate copy to R2 or S3. The users and their credentials travel, which removes the worst part, though Layerbase does not run a login service, so you still wire login against the migrated table on the new side.

I will cover the middle path in detail because it is still what a lot of Lovable teams reach for first, and the steps are concrete. The full-move auth import has its own walkthrough in Migrating from Supabase to Layerbase.

Migrating the database

The migration is the same shape as any Postgres-to-Postgres move: dump from Supabase, restore to the new host, repoint the connection string.

Layerbase has a built-in flow that runs the dump and restore for you, and imports your Supabase auth users in the same pass. In the dashboard, choose New database, then Migrating from another platform, then Supabase. Paste a Supabase access token, pick the project, paste its database password once, and it copies the schema, the data, and the users. That is the path most people want; the walkthrough is in Migrating from Supabase to Layerbase.

If you would rather run the dump by hand, the manual steps are below.

Step 1: Provision the new database

Visit layerbase.com/create/postgresql and create a Postgres named for the project (lovable-app-prod). Provisioning is about ten seconds.

Copy the connection string. It looks like:

text
postgresql://layerbase:<password>@your-host.cloud.layerbase.dev:5432/app?sslmode=require

Step 2: Dump from Supabase

Supabase exposes a direct connection string in your project dashboard (Settings, Database, Connection string, "URI"). Use it with pg_dump:

bash
pg_dump \
  --no-owner --no-acl \
  --schema=public \
  --schema=storage \
  "postgresql://postgres:<password>@db.<project>.supabase.co:5432/postgres" \
  > supabase-dump.sql

A few things:

  • --no-owner --no-acl strips the Supabase-specific ownership and grants. Without these flags, the restore will try to GRANT things to roles that do not exist on the destination and the whole restore will fail.
  • --schema=public --schema=storage dumps just your application tables and the storage tables. The auth schema stays on Supabase if you are following the middle path.
  • Run from a machine with the same Postgres major version as Supabase (currently 17). Mismatched pg_dump versions can produce dumps the destination cannot read.

Step 3: Restore to Layerbase

bash
psql \
  "postgresql://layerbase:<password>@your-host.cloud.layerbase.dev:5432/app?sslmode=require" \
  < supabase-dump.sql

For a small database (under 1GB) this takes a couple of minutes. For larger databases use pg_restore with -j 4 for parallel restore:

bash
# If you used pg_dump -Fc (custom format) instead of plain SQL:
pg_restore \
  --no-owner --no-acl -j 4 \
  -d "postgresql://layerbase:<password>@your-host.cloud.layerbase.dev:5432/app?sslmode=require" \
  supabase-dump.dump

Step 4: Repoint Lovable

In your Lovable env settings, change DATABASE_URL to the new connection string. If your generated code uses the Supabase JS client for database access (not just auth), replace it with postgres or pg. See Connect a Postgres database to a Lovable app for the exact code.

Step 5: Verify and cut over

Before you flip production, run the app against the new database in a staging environment. Spot-check the row count for each table:

sql
select schemaname, relname, n_live_tup
from pg_stat_user_tables
order by n_live_tup desc;

The counts should match Supabase. If they do, flip production by deploying the env var change. If they do not, find out why before flipping.

Keeping auth working during the move

You have two options here, and the second one got a lot easier recently.

Bring auth with you. Migrating the Supabase project to Layerbase copies auth.users across with the bcrypt password hashes untouched, so you are not forced to reset everyone, and you manage the accounts from the Auth console. The user IDs (UUIDs) are preserved, so the user_id columns in your tables still match and the foreign keys below are not a problem. The one caveat: Layerbase does not run a login service, so you wire login against the migrated table yourself. Migrating from Supabase to Layerbase covers exactly how, including why this is not the Better Auth wizard.

Leave auth on Supabase. Still reasonable if you only want to move the database and not think about auth yet. The auth table lookups keep going to Supabase while your application queries hit Layerbase. The two are decoupled.

If you go the leave-it route, the piece that needs attention is foreign keys. If your application tables have FOREIGN KEY (user_id) REFERENCES auth.users(id), that foreign key cannot exist on Layerbase because the auth.users table is on Supabase. Drop those foreign keys:

sql
alter table public.posts drop constraint if exists posts_user_id_fkey;
alter table public.comments drop constraint if exists comments_user_id_fkey;
-- and so on for each table that references auth.users

You lose database-enforced referential integrity for the user reference. You can replace it with application-level checks (validate the user exists before insert) or accept the looser coupling. Most teams accept it because the auth side controls user creation.

The user IDs themselves stay the same (UUIDs from Supabase). All your user_id columns continue to work.

Row-level security policies that reference auth.uid() need to be reimplemented in your application code, because auth.uid() is a Supabase-specific function. This is the part that takes the most work. In practice, most Lovable apps use RLS lightly (a few "user can only see their own rows" policies) and replacing those with application-level filters takes an afternoon.

What you do not need to migrate

A short list to keep the scope clear.

  • Auth users. Optional. You can bring them to Layerbase with their password hashes (no resets), or leave them on Supabase if you only want to move the database for now. The middle path just means you do not have to move them yet, not that you cannot.
  • Storage objects. If you use Supabase storage and want to leave it, that is a separate migration. Most teams leave it in place for the same reason as auth: the cost is low and the migration is annoying.
  • Edge functions. Lovable's deploy story does not depend on Supabase edge functions. If you wrote any (you probably did not), they need to be reimplemented as API routes in your Lovable app.
  • Realtime subscriptions. If you use Supabase realtime, you will need a replacement. Postgres LISTEN/NOTIFY plus a thin WebSocket layer works. Or skip it and use polling. Realtime is the part of Supabase that is hardest to leave; if you depend on it heavily, the middle path may not be the right one.

Wrapping up

The short version of "do I move and how":

  • If the Supabase bill is $25 per month and you are happy with the experience, do not move. There is no win.
  • If the bill is climbing past $100 per month and growing, run the migration. It pays for itself.
  • Move the database first. This is the lowest-risk, highest-leverage step.
  • Bring the auth users along if you want to leave Supabase entirely. The hashes migrate, so it no longer means a forced reset for every user. Or keep auth on Supabase for now and move it later. Both are fine.

The destination is your choice. Layerbase Cloud gives you plain managed Postgres with room to add Redis and Qdrant later if your app grows that way. Neon gives you branching for preview deploys. A $5 VPS gives you a fixed bill and operational work. All three are valid, and the migration steps above are identical for any destination that speaks Postgres.

For local development against the new setup, SpinDB runs Postgres locally and accepts the same dump file. Develop against a local copy, deploy against the cloud one.

Something not working?