How to Self-Host Supabase on Elestio (Full Setup Guide)

How to Self-Host Supabase on Elestio (Full Setup Guide)

Supabase gives you a Postgres database, authentication, instant REST and realtime APIs, file storage, edge functions, and an admin studio, all from one open-source stack. The hosted version is great until you hit the moment every growing team hits: you want your data on infrastructure you control, with no row limits, no pausing inactive projects, and no per-seat pricing creeping up as you add developers. The good news is the entire Supabase stack is open-source and runs perfectly well on your own server.

This guide walks through deploying a fully managed, self-hosted Supabase on Elestio and wiring it into your app. You get the same developer experience as the cloud version, on a dedicated VM that's yours.

What you get

Self-hosted Supabase is not one program, it's a coordinated stack that Elestio deploys and keeps running together:

  • Postgres - the database at the center of everything, with real SQL and extensions.
  • Studio - the web dashboard for managing all of it.
  • Kong - the API gateway that fronts every service on one URL and routes /auth, /rest, /realtime, and /storage to the right place.
  • GoTrue (Auth) - email, password, magic-link, and OAuth sign-in.
  • PostgREST - turns your tables into a REST API automatically.
  • Realtime - websocket subscriptions to database changes.
  • Storage + imgproxy - S3-style file storage with on-the-fly image transformation.
  • Edge Functions - Deno-based serverless functions running next to your data.
  • Supavisor - a built-in connection pooler, so you don't bolt one on later.

Elestio also wires up the supporting pieces (postgres-meta, Logflare analytics, and a log shipper) so the whole thing behaves like the managed Supabase you already know. On the hosted plan these are someone else's to scale and price. Self-hosted, they're yours, and the only bill is the server they run on.

Step 1: Deploy Supabase on Elestio

Head to the Supabase service on Elestio and click Deploy. You'll choose:

  • Provider and region: Netcup, Hetzner, DigitalOcean, Linode, Vultr, ScaleWay, or bring your own AWS / VM. Pick the region closest to your users.
  • Service plan: Supabase runs a dozen services at once, so give it room. Start at 2 CPU / 4 GB RAM minimum; 4 CPU / 8 GB is more comfortable once real traffic arrives.

Click Deploy and Elestio provisions the whole stack for you, with SSL, automated backups, and monitoring already configured. There's no environment file to assemble and no JWT secrets to generate by hand, the platform handles that part. When the service goes live, you'll have a public URL fronted by the Kong gateway and access to Studio.

Step 2: Open Studio and grab your API keys

Once the service is running, open Supabase Studio at your Elestio service URL. Elestio guards the dashboard at its reverse proxy, so you'll sign in with the username root and the admin password shown in your Elestio dashboard. That keeps Studio off the open internet from the first second.

Inside the service details you'll also find the two keys every Supabase app needs:

  • anon key - safe to ship in client-side code. It's restricted by your Row Level Security policies.
  • service_role key - full access, bypasses Row Level Security. Server-side only, never put this in a browser bundle.

Copy these from your own instance rather than from any example online, they're unique to your deployment.

Step 3: Connect from your app

Install the official client library:

npm install @supabase/supabase-js

Initialize it with your instance URL and anon key:

import { createClient } from '@supabase/supabase-js'

const supabase = createClient(
  'https://your-supabase.vm.elestio.app',
  'YOUR_ANON_KEY'
)

From here, the API is identical to hosted Supabase. Sign a user up:

const { data, error } = await supabase.auth.signUp({
  email: 'dev@example.com',
  password: 'a-strong-password'
})

Query a table (RLS still applies):

const { data: posts } = await supabase
  .from('posts')
  .select('id, title, created_at')
  .order('created_at', { ascending: false })
  .limit(10)

Upload a file to Storage:

const { data, error } = await supabase
  .storage
  .from('avatars')
  .upload('user-123/photo.png', file)

Because it's the same API surface, any existing Supabase code or tutorial works against your self-hosted instance by changing only the URL and keys.

Step 4: Lock down keys, RLS, and signups

Studio is already protected by the reverse-proxy login from Step 2, so the dashboard isn't your exposure. A few habits matter more on a self-hosted instance than on the cloud version, where some of this is handled for you:

  1. Keep and rotate the dashboard credentials. The root login from Step 2 is the front door to everything Studio can do. Store it somewhere safe and change it if it ever leaks.
  2. Treat the service_role key like a root password. Keep it in server-side environment variables only. If it lands in client code, anyone can read and write your entire database regardless of RLS.
  3. Turn on Row Level Security for every table that holds user data. The anon key is only safe because RLS policies decide what it can touch. A table without RLS is wide open to anyone holding the anon key.
  4. Disable open signups if you don't want them. Self-hosted Auth allows email signups by default. If your app provisions its own users, turn signups off so strangers can't create accounts against your instance.

A note on connecting at scale

Because the stack ships with the Supavisor pooler, you have two ways to reach Postgres: a normal session connection and a transaction pooler. If you're connecting from serverless functions or anything that opens a flood of short-lived connections, use the transaction pooler endpoint rather than the direct port. It's the same reason you'd put PgBouncer in front of a bare Postgres, except here it's already running.

Common pitfalls

Auth or queries return 401 right after deploy. Your app is probably using a key from a different instance or an old copy. Re-copy the anon and service_role keys from your current Elestio instance.

Sign-up works but the confirmation email never arrives. Self-hosted Auth needs SMTP credentials to send confirmation and magic-link emails. Add your SMTP details in the Auth configuration, or enable auto-confirm while you're testing so accounts activate without an email.

File uploads fail over about 50 MB. Storage ships with a default file size limit. Raise it in the Storage settings if your app handles larger files.

Realtime subscriptions never fire. Realtime rides on websockets. If you've put a custom proxy in front of Supabase, make sure it forwards Upgrade and Connection headers so websocket connections aren't dropped.

A query works in Studio but not from your app. Studio acts with elevated access; your app uses the anon key. The difference is almost always a missing Row Level Security policy. Add a policy that grants the access you intend.

Pointing your domain at it

Once everything works on the default Elestio URL, map your own domain so your API lives at a clean address like api.yourapp.com. Follow Elestio's custom domain and automated SSL guide, which handles the certificate for you.

That's the whole setup: a complete, self-hosted backend with auth, database, storage, realtime, and edge functions, running on a dedicated VM that Elestio keeps patched, backed up, and monitored. You write the app; the infrastructure is handled. Deploy your Supabase instance here and connect your first app this afternoon.

Thanks for reading ❤️ See you in the next one 👋