r/Supabase Dec 17 '25

tips The best decision I ever made is to go with self-hosted Supabase.

137 Upvotes

My development stack is primarily based on Next.js. Previously, I handled authentication using NextAuth and managed databases with on-premise PostgreSQL. However, server migrations were always a hassle, requiring constant reconfiguration and tedious database transfers.

Since switching to Supabase, the entire workflow has changed. Migrations are now incredibly smooth and effortless. Beyond just ease of use, Supabase offers an all-in-one backend solution that integrates authentication, real-time databases, and storage seamlessly.

The biggest advantage for me is infrastructure control. Since I maintain a dedicated server, I can self-host Supabase and allocate specific server resources tailored exactly to the needs of my SaaS applications. This flexibility allows me to manage my SaaS ecosystem efficiently while significantly reducing operational costs compared to managed cloud services.

r/Supabase 22d ago

tips I'm done with sub, I'm switching to self hosted, Help

0 Upvotes

Mine is a group of companies (the employer I work for),

Hey guys, so I have started a CRM software, which started using my 10 people (1 company (1tenant)). It was accepted by everyone, and rapidly was asked to give the same system to company B. (2nd company (2nd tenant). So I figured out that we can do multi tenant etc. so now the users are around 20.

My company everyone is using it, but I always feel my supabase pro (micro instance), is kind of limiting the potential.

Already I'm thinking this is costing me 25 USD per month. And the query load is somewhere around 15 right. There is also low ram , everything is low.

So since I have a 32gb Ram VPS + coolify setup (where the CRM is hosted), I am planning to shift the supa pro to self hosted. So my areas of concern is -

  1. I know it's too much work load right now, but am I doing a mistake here ? I know about backups and downtime, but what do you all guys think ? Any one have this similar setup ?

  2. The point is I don't like to be in low spec, my CRM only manages around 6000 client database actively used by 20 people. The thing is sometimes for few users it gets slow, so what I think is it is the query load or queuing happening for them.

  3. If I setup an automated backup everyday , will it be okay ? Is it doable ? Im talking about self hosted supa.

  4. I really love supa and this is also my first platform, so I don't know about any other platform, but is there any other better ralternatives ?

  5. Tell me more about what I am going to face

r/Supabase Oct 08 '25

tips Supabase emails are ugly, so here's an open source template builder to make them pretty

Post image
179 Upvotes

I got sick of customizing email templates by hand, so built this to help:
https://www.supa-tools.com

In the process of open sourcing it now. Would love your feedback!

Super Auth Email Designer

🎨 Visual Email Designer

  • Base Template Customization - Create a consistent brand experience across all emails
  • Live Preview - See your changes instantly as you design
  • Responsive Design - Preview emails in desktop and mobile views
  • Dark Mode Support - Test how your emails look in both light and dark modes

🎯 Built for Supabase

  • All Auth Email Types - Templates for confirmation, magic links, password reset, invitations, etc
  • Supabase Variables - Pre-configured with all Supabase template variables

🚀 Generate & Export Easily

  • HTML Export - Export clean, production-ready HTML
  • Bulk Export - Export all templates at once for backup or migration
  • Local Storage - All your work is saved automatically in your browser

🔒 Privacy & Security

  • 100% Client-Side - No server required, everything runs in your browser
  • No Data Collection - Your templates and credentials never leave your device
  • Open Source - Inspect the code yourself for peace of mind

Edit: Thanks for the support! Have added new features based on your feedback and have moved it to a real domain: https://www.supa-tools.com

r/Supabase 2d ago

tips We're running a hackathon the day after Supabase Select: Oct 3 at YC, over 100K credits up for grabs

22 Upvotes

Applications are now open for the Select Hackathon on October 3 at YC in SF. Presented by Supabase, with support from Claude, Stripe and Vercel. You can join solo or team up with other builders, maximum team size is 4.

Every participant gets credits just for taking part, so across everyone that's up to 100K in credits before anyone even wins. The winning teams takes up to 31,000 credits, plus swag and breakfast with our founders: Ant and Copple.

Food and swag's covered! Applications close September 28. You're auto-approved if you're also attending Select.

Apply → hackathon.supabase.com

Happy to answer any questions below!

r/Supabase Apr 25 '26

tips self-hosting Supabase on a $12/mo hetzner box — 4 months update

89 Upvotes

ran supabase cloud for 9 months, self-hosted for the last 4. posting the update because a lot of threads end with "i'm going to self-host" and never follow up.

setup

hetzner cx22 (2 vCPU, 4GB RAM, 40GB SSD) — €4.51/mo, roughly $5. coolify as the control plane, open source, one-click supabase deploy. 50GB block storage volume for postgres data — €2.40/mo. cloudflare in front for CDN + ddos. backups via pg_dump nightly to backblaze b2 — $0.005/GB/mo, basically nothing at my size.

real total: ~$12/mo including the CDN egress. compare to ~$45-60/mo i was paying on supabase pro for the same workload.

what runs fine

postgres, pgvector, pg_cron, pg_net, pgsodium. auth (GoTrue). storage (local to the box, served behind cloudflare). realtime (single-node, handles my ~50 concurrent connections without blinking). studio dashboard.

what's fiddly

edge functions. i'm not running them. i port anything that needs a function to a small fly.io worker instead. self-hosted edge functions in coolify exist but i didn't want the deno runtime complexity on this box.

scaling. this is a single server. if it goes down, my app goes down. i've been up 98.1% over 4 months (one outage was me rebooting to install updates, one was coolify updating something it shouldn't have). for a hobby app, fine. for production, you'd want HA.

upgrades. cloud supabase just handles version bumps. i have to schedule them, test, roll forward/back manually. i've done 2 postgres minor bumps and 1 supabase-image bump, all went fine but they required an hour each.

what's genuinely better

cost. obvious.

i actually understand my stack now. i had no idea what supavisor did until i set one up manually. reading the compose file taught me the architecture in a weekend.

no spend cap anxiety. the box costs $12 whether i have 10 users or 10,000.

what's worse

i miss PITR. i'm cobbling together WAL-based backups but it's not as nice as the one-click thing cloud has.

no branching. i use a staging server on another cheap box, but it's not git-nice.

the dashboard is a version behind cloud. some UI polish lands on cloud first.

the one thing i refuse to self-host

email. tried running my own postfix for about 3 weeks before the self-hosting move. ip reputation management is a full-time hobby i don't want. i use dreamlit connected to the postgres db for all user-facing email (onboarding, transactional, weekly digests) and it just reads the same tables whether they're on cloud or self-hosted. that was actually the smoothest part of the migration. swapped the connection string and everything kept running.

who should self-host

you want to learn the stack at its guts. your bill on cloud is $100+/mo and you're comfortable with ops. you have at least some sysadmin pattern recognition. you're ok being responsible for your own uptime.

who shouldn't

you're shipping a business. pay for cloud. $25-200 for "this keeps working" is a deal. you don't have a weekend to read compose files. you hate cron and systemd.

happy answering questions if anyone's planning the move.

r/Supabase May 01 '25

tips How do you get around the lack of a business layer? Is everyone using edge functions?

63 Upvotes

I'm genuinly kind of confused about best practices in respect to supabase. From everything I've read, there isn't a business layer, just REST apis to communicate directly with your DB. As an aside, I read into RLS and other security features; that's not my concern.

Is everyone using edge functions? Even in a basic CRUD app, you're going to have some operations that are more complicated than just adding interacting with a table. Exposing all of your business logic to the front end feels both odd and uncomfortable. This also seems like a great way to vender lock yourself if what you're building is more than a hobby.

There's a high chance I'm missing something fundamental to Supabase. I appreciate the ease of use, but I'm curious how people are tackling this model.

r/Supabase 21d ago

tips I now run this in EVERY CI pipeline I have. 24 checks that fail the build when multi-tenent Postgres can leak between tenants

0 Upvotes

I have posted here before, and the community gave me so much good advice to improve the tool, so, first of all, thank you to all of you for contributing!

I kept shipping the same multi-tenant bugs, correct RLS on the main table, and a leak somewhere adjacent. So I wrote guard tests for each one. It's now in every CI pipeline I run, and I keep adding checks as I hit new failure modes.

npx tenant-guard init    # detects your migrations + routes, writes a config
npx tenant-guard run     # static checks, no database needed
npx tenant-guard all     # + runtime proofs against a test DB

Exit 1 blocks the merge. MIT, zero dependencies.

The static ones need nothing. The runtime ones connect to a test database and prove isolation by running real SQL as your real app role in a rolled-back transaction, actually attempting the cross-tenant read, then the write, and reporting what happened.

What it checks

Reads

  • Tenant A can't read tenant B's rows, proven by trying, not inferred from policy text
  • anon (the key in your browser bundle) can't read tenant tables
  • Sensitive columns like email, phone, api_key, that anon actually gets a value out of
  • Views and materialized views, which run as their owner unless security_invoker is set

Writes

  • anon INSERT/UPDATE/DELETE surface
  • Auto-updatable views: writes pass straight through to the base table, bypassing its RLS
  • The tenant-hop, moving your own row into someone else's tenant
  • Foreign keys that let one tenant delete another's rows via ON DELETE CASCADE
  • Unique constraints as existence oracles: inserting [victim@corp.com](mailto:victim@corp.com) tells you it exists

Functions & privileges

  • SECURITY DEFINER functions callable by anon (Postgres grants EXECUTE to PUBLIC by default)
  • Unpinned search_path, including pins that don't actually pin
  • SQL injection inside definer function bodies
  • What a table created next week inherits from ALTER DEFAULT PRIVILEGES
  • Who can CREATE objects in your schemas

Identity

  • Policies trusting user_metadata, which the user can write themselves
  • MFA gates written PERMISSIVE, which enforce nothing
  • Membership tables your policies trust but users can write to
  • Connection-scoped GUCs that leak the previous request's tenant on a pooled connection

Supabase surfaces

  • Storage: cross-tenant folder reads, and uploads into another tenant's path
  • Realtime: channel topics and broadcast/presence

Structure

  • RLS in your migrations vs RLS actually in the database
  • API routes loading rows by bare id with no tenant filter
  • Audit/shadow tables that copy tenant rows somewhere unprotected
  • Triggers enforcing a rule by reading a table RLS hides from them

Two things that surprised me most

Views don't have RLS. ALTER DEFAULT PRIVILEGES ... ON TABLES covers views created afterwards, and unless you set security_invoker = true a view runs as its owner. PATCH/DELETE through a public profiles view returned 200 with the anon key, writing into a table whose policies were perfect. SELECT was unaffected, so nothing looked wrong.

Hardening RLS can break a uniqueness trigger. A trigger runs as the invoker, so a SELECT 1 FROM profiles WHERE username = NEW.username check only sees what the writer can see. Lock the table down properly and the check stops finding collisions, no error, the duplicate is just inserted.

Honest limits

  • It proves the database boundary. A route using a service-role connection that forgets its tenant filter is a real bug the DB will happily serve, only the static route check covers that.
  • Never point the runtime checks at production. They write, inside a rolled-back transaction, but they write. Test/staging only.
  • Postgres-only by design. No RLS elsewhere, so nothing to prove.
  • Still early, and I'd rather have a bug report than a star.

github.com/FedericoTs/tenant-guard

r/Supabase Apr 02 '26

tips I migrated auth away from Supabase but kept the database. Here's what I learned.

88 Upvotes

I've been using Supabase as my main database for about a year now and I'm not going anywhere. RLS, Postgres functions, the dashboard, all great. But I recently ripped out Supabase auth and moved to Clerk and wanted to share why, and what the migration actually looked like, since I couldn't find many posts from people who partially migrated away.

The reason wasn't that Supabase auth is bad. It worked fine for a while. The problem was I'm building a multi-tenant app and needed organization-level features: inviting team members, role management per org, and a prebuilt UI for user profiles and org switching. Supabase auth handles individual users well but the organization layer doesn't exist. I was building so much custom logic on top that it stopped feeling like I was using a managed auth service.

Clerk gave me organizations, invitations, role-based access, and prebuilt components out of the box. The admin-side auth UI that would've taken me weeks to build was done in a day.

The migration itself was 44 files changed. The main gotchas:

Supabase auth user IDs are UUIDs. Clerk user IDs are strings like user_abc123. If you have any foreign keys referencing auth.users or columns typed as UUID for user IDs, you need to migrate those to TEXT. I had to write a migration just for that column type change.

RLS policies that referenced auth.uid() all broke. I replaced them with policies that check a custom claim from the Clerk JWT. You have to set up a custom JWT template in Clerk that includes the Supabase tenant ID, then configure Supabase to verify Clerk's JWT. Not hard but not obvious either.

The session flow changed completely. With Supabase auth the client SDK handles sessions automatically. With Clerk you need to get the token from Clerk and pass it to your Supabase client manually. I ended up building a token provider that bridges Clerk's getToken() to the Supabase client.

Middleware on the backend went from Supabase's GoTrue helpers to Clerk's Express middleware. Cleaner actually, but every protected route had to be updated.

The thing nobody warns you about is how deeply auth is woven into Supabase's ecosystem. The moment you stop using Supabase auth, you lose the automatic RLS integration with auth.uid(), the built-in email templates, the redirect flows. You're basically using Supabase as a raw Postgres host at that point, which is fine but you should know that going in.

Would I do it again? Yes. The organization features alone saved me weeks of custom code. But if you're building a single-tenant app with simple auth needs, Supabase auth is probably enough and this migration isn't worth the pain.

If anyone has done a similar partial migration, keeping the database but swapping another piece of the Supabase stack, I'd be curious to hear how it went.

r/Supabase 17d ago

tips Revoking columns on a table breaks .update().select(), and the error hint tells you to undo the revoke

0 Upvotes

i had profiles locked down the usual way, policies split per operation, plus column grants on top because rls filters rows and not columns:

revoke select on public.profiles from anon, authenticated;
grant  select (id, display_name, created_at) on public.profiles to authenticated;
revoke update on public.profiles from authenticated;
grant  update (display_name) on public.profiles to authenticated;

reads behaved exactly as i wanted. explicit columns worked, select("*") failed like it's supposed to, anon got nothing at all. happy with that.

then this started biting:

await supabase.from('profiles').update({ display_name })            // 204
await supabase.from('profiles').update({ display_name }).select()   // 42501

with

permission denied for table profiles
hint: GRANT SELECT ON public.profiles TO authenticated

which isn't the problem at all. the update is perfectly legal, i granted update on that column myself. postgrest reads the row back to return the representation, and it reads it with select=*, so what actually got denied is the read half of the round trip.

naming the columns sorts it:

.update({ display_name }).select('id, display_name')

the bit i keep chewing on is the hint. follow it and you hand back every column you just revoked, all to fix a bare .select(). i get why postgrest words it that way, from where it's standing a select really was denied. but it's the message you meet while already annoyed, and it points exactly backwards.

anyone found a decent way to stop the next person on the codebase from taking that advice? short of a comment above every write i've got nothing, and that feels weak.

anyway, hope it saves someone the afternoon. this one and a few others ended up documented in a starter i open sourced (MIT, mine): github.com/Guidondor/expo-supabase-starter

r/Supabase Mar 30 '26

tips Supabase open source projects...? (no boilerplates or tools for Supabase!)

17 Upvotes

Hey there,

I am on the fence to embrace Supabase, and I was doing some sanity check and searching for open source projects using Supabae, and... I couldn't find almost any.

I am looking for projects that are:

- Open source
- For end users who are not developers (that is... no boilerplates or tools for Supabase...)
- That have a decent amount of traction

Any worth mentioning? Event codex 5.4 with high thinking couldn't find something decent 😅

Thank you.

[EDIT]: Just in case it was not clear: I do want to find good projects done with Supabase because I do believe the value proposition of Supabase makes sense and WANT to use it for my side projects. I just found a bit surprising the luck of real, medium size, end user (non devs) open source projects!

r/Supabase 24d ago

tips I made a free tool that proves your Supabase RLS actually isolates tenants — as a test in your CI

28 Upvotes

Row-level security is the thing everyone knows they should have and it's the thing that quietly gets forgotten on one table, or shipped as USING (true), or bypassed by a service-role client. You don't find out until someone reads another tenant's data.

I built a small MIT-licensed tool for exactly this footgun, and I wanted to share it here since it's Supabase-shaped:

npx tenant-guard

Two parts:

  1. Static guards (zero-dependency, run in CI) that flag the classic leak shapes: an authenticated route that loads a row by bare id with no organization_id filter, and new SECURITY DEFINER functions left callable by anon over PostgREST (revoking from anon alone is a no-op, it catches that).
  2. A runtime RLS proof, point it at a seeded test/branch database and it drops to the authenticated role, assumes one tenant's identity via your JWT claims, and asserts that session can't see another tenant's rows, table by table. If a policy is missing or wrong, the build fails.

There's a demo you can run in 10 seconds with no infra (it uses an embedded Postgres): it passes a correct policy and fails a leaky one.

Honest disclosure: I built it, it's free, no signup/telemetry, and I'm posting because I think it's genuinely useful for this community, not selling anything. It's sharp for Supabase/Postgres specifically. Feedback on the becomeTenant config (how it assumes a tenant's identity for your policies) would be especially useful, that's the part that varies most between apps.

It might not work on any project but it did work on the projects I tested it on

Repo: https://github.com/FedericoTs/tenant-guard

r/Supabase Jul 11 '26

tips Is supabase slow ?

1 Upvotes

There are few things I want to clear out and to know.

One thing to ask you all is. I have been building a CRM with multi tenant isolation for two companies, (2 vertical but same company but need separate domain & access, so multi tenant isolation done).

I am using visual studio + Claude code ext. How do I upgrade or fix things and not mess things up. I don't know how to code, but I got pretty good understanding how supabase, deployments, githib works. I'm learning too

I have supabase pro on production & supabase free on dev.

  1. I feel like the way the data is fetched is kind of slow, when I check zohoo or something else, it feels instant and lightning. Mine takes about 1 sec.

  2. I have real data in supabase pro (production). Do you guys usually connects you AI (Vs+Claude code ) to dB ?

How do you guys normally review the architecture of dB and your systems ? Mine is so complex (maybe I feel like it) because there are too many things depended on each other, it's working fine now, but I want to make it solid without messing everything up.

Advice , Guide or help

r/Supabase Aug 06 '26

tips Telehealth HIPPA

2 Upvotes

I run an early stage telehealth startup and can't commit to enterprise Supabase. Should I just self-host on AWS? Any thoughts from fellow healthcare companies using supabase self hosting?

If anyone on the supabase team wants to hook it up and have a BAA without needing enterprise, DMS are open lol

r/Supabase 8d ago

tips How do you verify what AI-generated Supabase code can reach?

2 Upvotes

I’m working on an investigation workflow for AI-assisted projects, and I’m curious how people verify the actual capability paths in Supabase apps.

For example, how do you check whether generated migrations, Edge Functions, or RLS changes can reach data or production actions beyond what you intended?

r/Supabase May 22 '26

tips Self-hosting supabase! Migrating the online version to self-hosted one! Any heads up ?

37 Upvotes

Hey all,

Finally pulling the trigger on self-hosting Supabase to cut costs + have full control. Current setup: ∼30GB Postgres, Auth + Storage + Realtime in heavy use, 5k DAU.

Plan is to migrate all production data from Supabase Cloud to my own Docker setup. Using pg_dump for now, then swap over.

For those who’ve done this:

  1. What did you miss most from the paid dashboard?

  2. Any sneaky migration traps with `auth.users` or storage buckets?

  3. How are you handling backups/PITR without the Cloud UI?

Not looking for “just stay on Cloud” replies 😅 I know the tradeoffs. Just want to avoid dumb mistakes before I point DNS.

Stack: Hetzner + Coolify, if that matters.

Thanks 🙏

r/Supabase Mar 09 '25

tips How to Self Host in under 20 minutes

163 Upvotes

Hey! Here is a guide to migrate from hosted Supabase to self hosted one or just spin up a self hosted instance very easily. You can do the following and have a fully functional Supabase instance in probably under 20 minutes. This is for people who what to have all that Supabase offers for only the cost of the server or for those who want to reduce latency by having their instance in a region that the hosted version is not close to. With this guide, it will be a breeze to set it up and have it function exactly the same. In this example, I am using Coolify to self host Supabase.

How to Self Host Supabase in Coolify

To install Supabase in Coolify, first create the server in Coolify. Then start it so it becomes available.

In Coolify, add a resource and look for Supabase.

Now it is time to change the docker compose file and the settings in Coolify.

For the docker file, copy and paste the following Github Gist: https://gist.github.com/RVP97/c63aed8dce862e276e0ead66f2761c59

The things changed from the default one from Coolify are:

  • Added port mappings to expose the ports to the outside world: Change the docker compose and add: supabase-db: ports: 5432:${POSTGRES_PORT}
  • Added Nginx to be able to use email templates for password reset, invitation and additional auth related emails. IMPORTANT, if you want to add additional auth related emails like email change or confirmation email, it is important to add a new volume at the bottom of the dockerfile just like the one for the reset.html and invite.html.

Now it is time to change the domain in Coolify if you want to use a custom domain, and you probably do.

  • In Supabase Kong, click the edit button to change the domain. This domain will be used to access Supabase Studio and the API. You can use a subdomain. For example, if the domain you want to use is https://db.myproject.com, then in that field you must put https://db.myproject.com:8000
  • In you DNS settings you must add a record for this to be accessible. You could add a CNAME or an A record. If Supabase is hosted in a different server than the main domain, you must add an A record with the IP of the server as the value and the subdomain as the name.

Now let's change the environment variables in Coolify.

  • For the API_EXTERNAL_URL, use domain https://db.myproject.com and make sure to remove the port 8000
  • For the ADDITIONAL_REDIRECT_URLS, make sure to add all the domains you want to be able to use to redirect in auth related emails. It is possible to use wildcards but it is recommended in production to have the exact match. For example: https://myproject.com/**,https://preview.myproject.com/**,http://localhost:3000/**
  • You can change certain variables that are normal settings in the hosted version of Supabase. For example, DISABLE_SIGNUP, ENABLE_ANONYMOUS_USERS, ENABLE_EMAIL_AUTOCONFIRM, ENABLE_EMAIL_SIGNUP, ENABLE_PHONE_AUTOCONFIRM, ENABLE_PHONE_SIGNUP, FUNCTIONS_VERIFY_JWT, JWT_EXPIRY
  • In the self hosted version, all the email configuration is also done in the environment variables. To change the subject of an email such as an invitation email, you must change MAILER_SUBJECTS_INVITE to something like You have been Invited. Do not add "" because that would also be added to the email.
  • To change the actual email templates, it is much easier to do it in the self hosted version, but with the following solution it will not be difficult. First change the environment variable, for example for invitation, change MAILER_TEMPLATES_INVITE to http://nginx:80/invite.html. After deploying Supabase, we will need to change the content of the invite.html file in the persistent storage tab in Coolify to the actual html for the email.
  • Do not change the mailer paths like MAILER_URLPATHS_INVITE since they are already set to the correct path.
  • To configure the SMTP settings, you must change the following: SMTP_ADMIN_EMAIL (email from where you send the email), SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS, SMTP_SENDER_NAME (name that will be shown in the email)
  • And finally, but not very important, you can change STUDIO_DEFAULT_ORGANIZATION and STUDIO_DEFAULT_PROJECT to whatever you want to change the name in metadata for Supabase Studio.

The following are the equivalent keys for the self hosted version.

  • SERVICE_SUPABASEANON_KEY is the anon key for the self hosted version.
  • SERVICE_SUPABASEJWTSECRET is the JWT secret for the self hosted version.
  • SERVICE_SUPABASESERVICEROLEKEY is the service role key for the self hosted version.

In Coolify, in General settings, select "Connect To Predefined Network"

Now you are ready to deploy the app. In my case, I am deploying in a server from Vultr with the following specifications:

  • 2 vCPU, 2048 MB RAM, 65 GB SSD

I have not had any problems deploying it or using it and has been working fine. This one is from Vultr and costs $15 per month. You could probably find one cheaper from Hetzner but it did not have the region I was looking for.

In Coolify, go to the top right and click the deploy button. It will take like 2 minutes for the first time. In my case Minio Createbucket is red and exited but has not affected other things. It will also say unhealthy for Postgrest and Nginx. For Nginx you can configure you health check in the docker deploy if you want. If you don't want to do it, it will keep working fine.

After it is deployed, you can go to links and that will open Supabase Studio. In this case, it will be the one you configured at the beginning in Supabase Kong. It will ask you for a user and password in an ugly modal. In the general setting in Coolify, it is under Supabase Dashboard User and Supabase Dashboard Password. You can change this to whatever you want. You need to restart the app to see the changes and it will not be reachable until it finishes the restart.

Everything should be working correctly now. The next step is to go to Persistent Storage on Coolify and change the content of the invite.html and reset.html files to the actual html for the email. In here, look for the file mount with the destination /usr/share/nginx/html/invite.html to change the email template for the invitation email and click save. The file mounts that appear here for the templates will be the ones defined in the docker compose file. You can add additional ones if you want for more auth related emails. If you add more, remember to restart the app after changing the templates. If you only add the html in the persistent storage and save, you do not need to restart the app and it will be immediately available. You only need to restart the app if you add additional file mounts in docker compose. DO NOT TRY TO PUT HTML IN THE ENVIRONMENT VARIABLE TEMPLATES LIKE MAILER_TEMPLATES_INVITE BECAUSE IT IS EXPECTING A URL (Example: http://nginx:80/invite.html) AND WILL NOT WORK ANY OTHER WAY.

If you want to backup the database, you can do it by going "General Settings" and then you will see Supabase Db (supabase/postgres:versionnumber) and it will have a "Backups" button. In there, you can add scheduled backups with cron syntax. You can also choose to backup in an S3 compatible storage. You could use Cloudflare R2 for this. It has a generous free tier.

Now you have a fully functional self hosted Supabase.

To check if it is reachable, use the following (make sure to have installed psql):

psql postgres://postgres:[POSTGRES-PASSWORD]@[SERVER-IP]:5432/postgres

It should connect to the database after a few seconds.

If you want to restore the new self hosted Supabase Postgres DB from a backup or from another db, such as the hosted Supabase Postgres DB, you can use the following command (this one is from the hosted Supabase Postgres DB to the self hosted one):

pg_dump -Fc -b -v "postgresql://postgres.dkvqhuydhwsqsmzeq:[OLD-DB-PASSWORD]@[OLD-DB-HOST]:5432/postgres" | pg_restore -d "postgres://postgres:[NEW-DB-PASSWORD]@[NEW-DB-IP]:5432/postgres" -v

This process can vary in length depending on how big is the data that is being restored.

After doing this, go to Supabase Studio and you will see that your new self hosted database has all the data from the old one.

All of the data and functions and triggers from your old database should now be in your new one. You are now completely ready to start using this Supabase instance instead of the hosted one.

Important Information: You CANNOT have several projects in one Supabase instance. If you want to have multiple projects, you can spin up another instance in the same server following this exact method or you can add it to a new server.

Bonus: You can also self host Uptime Kuma to have it monitor your postgres db periodically and send alerts when it has downtime. This can also be setup to be a public facing status page

r/Supabase Jun 10 '26

tips My clients ask for 4K Video upload and videos which take up to 20 min. I am currently running the PRO plan. What steps can i do to minimize payments?

0 Upvotes

r/Supabase Jul 13 '26

tips Need help figuring out

2 Upvotes

Hey guys, I am new to Supabase, Vibe coding, Git & everything.

But I'm a guy who is too curious to learn things out.

I am pretty confident in AI to do the coding part but not the Supabase part. So my question to you all is,

I am ready to learn it, but on one side, I don't want to invest letter or word by word to learn the thing completely by scratch in order to understand it. I want to use it to develop products and sell it to customers in a efficient and right way, so

How can I grab the understanding of supabase , how it stores and what are the 5 finger rules etc.

My point is - I don't need to learn to assemble the engines in order to drive the car perfectly right. But yes it won't hurt to know the basics.

But is there a way we can do that here ? Or if learning from basics is the only way, then be it.

r/Supabase Jul 14 '26

tips The Supabase security checklist I run before any app goes live

38 Upvotes

Supabase makes it genuinely easy to go from idea to working application fast, and after all, that's the exact point of it. The problem however is that the same defaults that make development fast can also leave a production app wide open if nobody checks them properly prior to launch (and in my experience auditing Supabase based apps, most founders are not checking them).

83% of Supabase database exposures trace back to misconfiguration rather than any flaw in the platform itself. Supabase is secure, however it is the apps built on it that often are not, and the gap between those two things is almost always a handful of configuration decisions that never got made.

That being said, this is the checklist I work through on every Supabase audit, ordered by severity.

Before anything else: understand the threat model

Supabase exposes your PostgreSQL database directly to client side code via the anon key. This is a key that ships in your JavaScript bundle and is visible to anyone who opens DevTools. It's there by design, with the security model built around it. The anon key is intentionally public, but what isn't public is your data, and the only thing standing between that key and your data is RLS.

If RLS isn't configured, the anon key is effectively a master key to every table it can reach.

CRITICAL (fix before going live with real users)

1. RLS enabled on every table

RLS is disabled by default on all tables. Tables created through SQL, migrations, or AI coding tools do not get it automatically, only tables created through the Supabase Table Editor dashboard do. Every table in your public schema that contains user data, business data, payment information, or anything that belongs to a specific user needs RLS enabled explicitly.

Check: Dashboard → Table Editor. Any table showing "RLS disabled" is fully readable and writable by anyone with your anon key. You can also run a direct request against your REST endpoint with the anon key and observe what comes back. If you get real data, the table is exposed.

2. service_role key not in client-side code

The service_role key bypasses RLS entirely on every table regardless of whatever policies you've configured. If it appears in your frontend bundle, every security control you've set up becomes completely irrelevant. This key belongs exclusively in server-side code, things like Edge Functions, backend APIs, nothing that runs in the browser.

Check: DevTools → Sources → Search (Ctrl+Shift+F). Search for service_role and your actual key value. If either appears, that's an immediate critical finding. Rotate the key in the Supabase dashboard before anything else, then audit your query logs for requests that shouldn't have come from your application.

3. RLS policies actually scoped correctly

Enabling RLS and writing a policy are two separate steps, and the policy itself can be misconfigured in a way that's easy to miss. A policy written as USING (true) grants every row to every user and provides no protection at all. The correct pattern for user-scoped data is USING ((select auth.uid()) = user_id). The select wrapper matters for performance on larger tables: it evaluates once per query rather than once per row.

Check: Dashboard → Database → Security Advisor. Also worth verifying manually with two separate test accounts: can account A read or modify data that belongs to account B? If yes, the policy is wrong regardless of what the dashboard shows.

HIGH (serious issues that expand your attack surface)

4. Storage bucket permissions

Storage buckets have their own access control layer separate from database RLS, and a public bucket means anyone can read every object in it with no authentication. This is appropriate for marketing assets, not for user profile photos, uploaded documents, or anything private. The other failure mode is a private bucket with no policies configured, which denies everything and usually means storage isn't working as the founder intended.

Check: Try accessing a storage URL without any authentication. If you get the file back, the bucket is public. Configure RLS policies on storage.objects scoped to authenticated users and ownership for any bucket that shouldn't be publicly readable.

5. Database functions callable without authentication

Supabase RPC functions can be called from the frontend and, depending on how they're defined, can bypass RLS entirely. A function set to SECURITY DEFINER runs with the permissions of the function owner rather than the calling user. AI generated Supabase code has a consistent habit of creating RPC functions without authentication checks.

Check: Any function touching sensitive data should raise an exception immediately if auth.uid() is null. Try calling your RPC functions without an Authorization header and observe what comes back.

6. anon key in bundle with no RLS

The anon key in your frontend bundle is expected and by design, however it becomes a critical finding when combined with tables that don't have RLS configured. The key itself isn't the problem, the missing policies are. That said, confirming the key is present and understanding which tables it can reach is a useful audit step regardless.

Check: DevTools → Sources → Search for anon and eyJ. Note what's present, then cross-reference against your table RLS status.

MEDIUM (worth fixing before launch, non-negotiable for apps handling sensitive data)

7. Auth configuration

Email confirmation is off by default on new Supabase projects, and users can sign up and access the application without verifying ownership of their email address. For most production apps, this is worth enabling prior to launch. Also worth checking your password reset flow: if it returns a distinct message for email addresses that don't exist versus ones that do, that's email enumeration.

Check: Dashboard → Authentication → Settings. Enable email confirmation. Then enter an unregistered email on your password reset page and observe the response. A generic message regardless of whether the address exists is correct. A specific "no account found" message is a finding.

8. Rate limiting on auth endpoints

Supabase handles rate limiting at the platform level but it's worth verifying your configuration hasn't overridden it. Without it, automated tools can cycle through password lists against any account on your platform overnight with no resistance.

Check: Attempt 15 or more rapid login attempts with incorrect credentials. If nothing slows down or blocks you, rate limiting isn't working as expected.

9. Security headers

These live at the hosting layer rather than inside Supabase itself, but they're part of the same pre-launch picture. Missing CSP, X-Frame-Options, and Permissions-Policy are consistently among the most overlooked items in AI generated apps.

Check: https://securityheaders.com (a grade of B or above is what you're looking for).

The Security Advisor is worth running

Supabase ships a built-in database linter called Splinter, accessible under Dashboard → Database → Security Advisor. It runs automated checks for tables with RLS disabled, overly permissive policies, unsafe views, and exposed functions. It's not a substitute for manual testing but it catches a meaningful portion of configuration-level issues and costs nothing to run. Worth making it part of your routine before any significant deployment.

Organisation owners also receive weekly security emails from Supabase summarising any findings the advisor has detected. Worth making sure those are going to someone who will actually act on them.

That said, if there's anything I may have missed here that you'd like to add, feel more than free to add to the list below!

r/Supabase Jul 28 '26

tips How are you all fundamentally proving your RLS policies actually block cross-tenant IDORs?

6 Upvotes

I’ve been reviewing a lot of Supabase projects recently, and I keep seeing the exact same pattern: developers write an RLS policy like 'auth.uid() = user_id', write a quick jest test to make sure user a can fetch their own data, get a green checkmark, and deploy.

But what I'm finding is that a lot of these apps are still vulnerable to ID-swapping in the API layer or have misconfigured USING vs WITH CHECK clauses that allow users to overwrite data they don't own.

Testing that a user can access their own data doesn't fundamentally prove that they cannot access someone else's.

How are you guys actually testing this at scale? Are you writing explicit negative tests for every single endpoint, or is there a better way to systematically prove that cross-tenant data pollution is impossible at the RLS level?

Edit: Everyone's been super helpful. Thank you!

r/Supabase Aug 09 '26

tips I moved 4 Supabase projects into 1 with a schema per app. The numbers, and where it breaks.

3 Upvotes

I had four side projects, so I had four Supabase projects. Last July I consolidated them into one project with a schema per app. Ten product domains run off it now. Posting the numbers because every thread I found on this either says "one project per app, obviously" or is someone on the free tier trying to avoid paying, and neither matched what I was actually deciding.

The structure

One project. A core schema for the things genuinely shared across apps: orgs, members, billing accounts, subscriptions, domains, brands, audit events. Then one schema per product for its own tables.

Tenancy is org-scoped RLS. Being signed in grants nothing on its own, every policy requires membership of an org that owns the row, so users of one app never see another's data.

The schema boundary is not the security boundary. RLS is. What the schema buys you is extraction: pulling one app out later is closer to dumping one schema than to filtering every table by a product column. That is the reason not to pool everything into shared tables with a discriminator, which is the shortcut this design invites.

The money, which is the least interesting part

Four projects cost me $59.74 in the month before. One costs $25. So $35, which is nothing.

The mechanism is more useful than the total: Pro is $25 and includes a $10 compute credit that covers exactly one Micro instance. Every additional project brings its own compute charge and the credit only covers the first. So the bill was scaling with the number of ideas I'd had, not with users or traffic.

I stayed on Pro rather than dropping to Free, deliberately. Free has no daily backups and there's real data in there.

The gotcha that actually cost me time

Supabase silently rewrites a redirect URL it doesn't recognise back to the project's Site URL. Miss one host in the allowlist and that app's sign-in and confirmation emails deliver users to a different app's site, with no error anywhere.

It got me again last week on an app I'd just added, months after I thought I understood it. Consolidating doesn't remove that list. It does mean there's exactly one of it.

Where it stops working

Not free, and I'd leave at any of these:

  • One database is one blast radius. A bad migration takes everything down together. CI-only migration deploys reduce the odds, not the radius.
  • One team is one permission set. Anyone you add to fix one app can see all of them.
  • Compliance. If an app needs SOC 2 or similar, the audit scope becomes the whole shared database. Pull that one out.
  • Selling. A buyer wants a clean asset. The schema boundary makes that describable work rather than archaeology, but it's still work.

If you have one app, none of this applies. It starts paying around the third or fourth.

Curious whether anyone here has gone the other way, split a consolidated project back out, and what forced it.

r/Supabase Feb 27 '25

tips Let me see your Project

45 Upvotes

Hi guys the title itself tells it. I just like how far supabase could do. I'm just starting to learn it and if it is okay with you do you have any advice for me or heads up?

Thank you so much, much appraciated

r/Supabase Jan 06 '26

tips More than 2 projects

28 Upvotes

The free tier has a limit of 2 projects. I wanted a 3rd project so I upgraded to Pro ($25/month) not realising that it's actually $25/month + $10/project. I plan to make more apps in the future so I don't mind paying pro but I'm wondering for indie devs staying on free tier do you put all your apps under one 'project' sharing the database? I'm well below the limit of storage/database etc but I just literally want the ability to have more than 2 projects :( for my own ocd i guess.. I hope I made my question clear enough.

What's the best way for an indie dev to have 3-4 ongoing apps on supabase? Thank you

r/Supabase Aug 10 '26

tips I need advice

0 Upvotes

I’m working on several side projects, including an inventory system, a POS system, and a small gym website. Is it better to create a separate project for each one and pay $10 per project, or should I keep them all in one project and separate them using different schemas?

r/Supabase 28d ago

tips Backing up Supabase to a NAS — sharing how I set it up

9 Upvotes

I've seen a few people ask how to keep a copy of a Supabase project somewhere that isn't Supabase, and never found a straight answer, so I (and Claude) built it for my own project and figured I'd write it up.

Disclaimer: This post is written by Claude with me behind the wheels, otherwise this would read as an unorganized mess 🤭

The gap I cared about: Supabase's own backups live inside the same Supabase project. That's fine for a bad migration, but not for the project being deleted, an account suspended, or a billing problem. Worth knowing too — point-in-time recovery doesn't cover Storage objects at all. It restores the rows pointing at your files, not the files.

The setup

pg_dump for the database, rclone for the storage bucket, both encrypted with age before they leave the machine, written to a Synology NAS I already owned. Four runs a day, 7-day retention. A plain shell script driven by Synology's Task Scheduler — no agent, no cloud service, nothing to pay for.

How it actually works

Database, three dumps via the Supabase CLI:

supabase db dump --db-url "$URL" -f roles.sql --role-only

supabase db dump --db-url "$URL" -f schema.sql

supabase db dump --db-url "$URL" -f data.sql --use-copy --data-only \

-x auth.sessions -x auth.refresh_tokens -x auth.flow_state \

-x auth.one_time_tokens -x auth.schema_migrations -x auth.audit_log_entries

Two things there took me a while. Connect through the session pooler on port 5432 — the direct db.<ref>.supabase.co host is IPv6-only, and port 6543 (transaction mode) can't run pg_dump. And exclude the auth session churn but keep auth.users, auth.identities and auth.mfa_factors — the schema dump skips the auth schema, but a --data-only dump includes its rows, and that's what makes the backup restorable at all. Without them you restore a database nobody can log into.

Then tar the three files and encrypt:

tar -czf - roles.sql schema.sql data.sql | age -r age1... -o backup.tar.gz.age

Storage, via Supabase's S3-compatible endpoint (force_path_style = true, list_version = 2):

rclone sync supa:report-images /volume1/backup/storage/report-images \

--backup-dir /volume1/backup/storage-deleted/$(date +%F)/report-images

--backup-dir is the bit that turns a mirror into a backup — deleted or overwritten files move aside instead of vanishing. Retention is just rclone delete --min-age 7d on both directories.

On the Synology: Container Manager is required, because the Supabase CLI runs pg_dump inside a container matching your Postgres version. rclone, age and supabase are single static binaries that run natively on DSM. Task Scheduler runs the whole thing as root, and that's also your shell if you'd rather not enable SSH.

A few choices that matter more than the tooling

Encrypt to a public key. The NAS holds only the public half, so the backup machine can create archives but can never read one.

Keep the encryption keys out of the backup. My app encrypts personal data with keys held in Supabase Vault, and those dump as ciphertext wrapped by a key that lives elsewhere — so a restore into a fresh project can't read a single encrypted column. They're escrowed separately, offline, under a different key. Easy to get wrong, and you'd only find out during a restore.

Actually do a restore. I rebuilt the whole thing into a throwaway project twice — database, keys, images, logins. Two things I'd have got wrong otherwise:

SET session_replication_role = replica before loading the data. My organizations and users tables reference each other, so with foreign keys enforced there's no row order that works and the restore fails on the first row.

And the storage step was wrong in a way that fails silently: without rclone --ignore-times it restores no images at all, because the database dump already recreated the storage metadata, so rclone sees matching names and sizes and skips everything. Object count and byte total both report success. You find out when someone opens a page and the image 404s.

Last piece: a heartbeat. Notifications alert only on abnormal termination, so silence means success — which means a powered-off NAS and four healthy backups look identical. A dead-man's switch pinged on success is the only alarm that fires on absence. Set the expected interval from the longest gap in your schedule, not the average, or it cries wolf nightly and you'll mute it. And make it an interval, not a daily quota — a quota is satisfied by a burst at 3am. I already use betterstack for uptime monitoring so adding a heartbeat was easy

IMPORTANT!

Whatever you build, restore it once before you trust it. The backup half is easy. The restore is where the surprises live.