Supabase on Elestio: Self-Host Your Backend with Auth, Realtime Subscriptions, and Edge Functions
Firebase changed the game for backend development. Then it changed its pricing, and suddenly that "free tier" wasn't looking so free anymore. If you've ever watched your Firebase bill climb while your app barely scaled, you already know why developers are looking for alternatives.
Supabase is the open-source answer. It gives you a PostgreSQL database, authentication, realtime subscriptions, edge functions, and storage, all wrapped in a developer-friendly API. And unlike Firebase, you can self-host the entire thing on your own infrastructure.
Here's how to deploy Supabase on Elestio and configure the features that actually matter for production.
What You Get Out of the Box
Supabase isn't a single service. It's an orchestrated stack of containers working together. Here are the core services you'll interact with:
| Service | Role | Internal Port |
|---|---|---|
| PostgreSQL | The database (the real star) | 5432 |
| PostgREST | Auto-generated REST API from your schema | 3000 |
| GoTrue | Authentication and user management | 9999 |
| Realtime | WebSocket-based change broadcasting | 4000 |
| Storage | S3-compatible file storage API | 5000 |
| Kong | API gateway routing all requests | 8000 |
| Studio | Web dashboard for managing everything | 3000 |
| Supavisor | Connection pooling for high concurrency | 6543 |
Additional services like Imgproxy (image processing), Logflare (analytics), and postgres-meta (schema introspection) run alongside these. Each container has its own internal port, so PostgREST and Studio both using 3000 is fine since they're isolated containers.
Kong acts as the single entry point. Every API call, whether it's a database query, an auth request, or a file upload, routes through Kong to the correct internal service.
Deploy on Elestio
Skip the manual Docker Compose wrangling. On Elestio, the entire stack deploys in under three minutes:
- Head to the Supabase service page
- Pick your cloud provider (Hetzner, DigitalOcean, Vultr, AWS, Linode, Scaleway)
- Select at least 4 CPU / 8 GB RAM (Supabase runs multiple containers, so 2 CPU won't cut it for production)
- Click Deploy
Elestio handles SSL certificates, automated backups, and system updates. You get a running Supabase instance with Studio accessible at your custom domain. For custom domain setup with automated SSL, follow the official Elestio documentation.
Set Up Row Level Security (The Part Everyone Skips)
Row Level Security is what makes Supabase genuinely production-ready. Without it, your PostgREST API exposes everything to anyone with the anon key.
Enable RLS on any table:
ALTER TABLE todos ENABLE ROW LEVEL SECURITY;
Then create policies that tie data access to authenticated users:
-- Users can only read their own todos
CREATE POLICY "Users read own todos"
ON todos FOR SELECT
USING (auth.uid() = user_id);
-- Users can only insert their own todos
CREATE POLICY "Users insert own todos"
ON todos FOR INSERT
WITH CHECK (auth.uid() = user_id);
-- Users can only update their own todos
CREATE POLICY "Users update own todos"
ON todos FOR UPDATE
USING (auth.uid() = user_id);
The auth.uid() function pulls the user ID from the JWT token automatically. No middleware, no custom auth logic. The database enforces access rules directly.
This matters because the same RLS policies apply everywhere: REST API, Realtime subscriptions, and Edge Functions. One policy definition, consistent enforcement across every access path.
Configure OAuth Providers
GoTrue handles authentication out of the box. To add Google OAuth, set these environment variables in your Elestio service configuration:
GOTRUE_EXTERNAL_GOOGLE_ENABLED=true
GOTRUE_EXTERNAL_GOOGLE_CLIENT_ID=your-client-id
GOTRUE_EXTERNAL_GOOGLE_SECRET=your-client-secret
GOTRUE_EXTERNAL_GOOGLE_REDIRECT_URI=https://your-domain.com/auth/v1/callback
Supabase supports GitHub, GitLab, Discord, Apple, Azure, and dozens more. Each provider follows the same pattern: enable it, add credentials, set the redirect URI.
After configuring, restart the auth service:
cd /opt/app
docker compose restart auth
Build Realtime Features
Realtime subscriptions broadcast database changes over WebSockets. Your frontend connects once and receives updates as they happen.
Here's how to subscribe to changes on a table using the JavaScript client:
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
'https://your-instance.elest.io',
'your-anon-key'
)
// Subscribe to new todos for the authenticated user
const channel = supabase
.channel('todos-changes')
.on(
'postgres_changes',
{
event: 'INSERT',
schema: 'public',
table: 'todos',
filter: `user_id=eq.${userId}`
},
(payload) => {
console.log('New todo:', payload.new)
}
)
.subscribe()
RLS policies apply here too. Even if a malicious client tries to subscribe to another user's data, PostgreSQL blocks it at the row level. No additional server-side filtering needed.
Edge Functions for Server-Side Logic
Edge Functions run Deno-based code on your Supabase instance. Use them for anything that shouldn't happen client-side: sending emails, processing payments, calling third-party APIs.
Create a function that triggers when a new user signs up:
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
serve(async (req) => {
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
)
const { record } = await req.json()
// Create a default profile for the new user
const { error } = await supabase
.from('profiles')
.insert({
id: record.id,
email: record.email,
created_at: new Date().toISOString()
})
return new Response(
JSON.stringify({ success: !error }),
{ headers: { 'Content-Type': 'application/json' } }
)
})
In a self-hosted setup, you manage Edge Functions through the Studio dashboard or by placing function files in the supabase/functions directory and restarting the functions container. Wire this function to a database webhook trigger on the auth.users table in Studio, and every new signup automatically gets a profile row. No client-side logic required.
Troubleshooting
High memory usage: Supabase runs 10+ containers simultaneously. If you're seeing OOM kills, bump to 8 GB RAM minimum. Monitor with docker stats inside your Elestio instance.
Realtime not broadcasting: RLS must be enabled on the table you're subscribing to. If RLS is off, Realtime silently skips the table. Enable it and add at least one SELECT policy.
Connection pool exhaustion: Under heavy load, PostgreSQL connections max out. Supavisor handles pooling automatically, but check your POOL_SIZE environment variable if you're seeing "too many connections" errors.
Auth redirects failing: Double-check GOTRUE_EXTERNAL_*_REDIRECT_URI matches your actual domain exactly, including the protocol (https, not http).
Studio not loading: Studio runs on a separate port internally. If it's unreachable, verify the Kong configuration in volumes/api/kong.yml includes the Studio route.
What This Gets You
A self-hosted Supabase on Elestio gives you a production backend with zero license fees, starting at $26/month for a 4 CPU / 8 GB RAM instance. Compare that to Supabase Cloud's Pro plan at $25/month per project with usage-based overages, or Firebase's Blaze plan where costs scale unpredictably with every read and write.
More importantly, you own the PostgreSQL database underneath. If you ever outgrow Supabase, your data is still standard Postgres. No proprietary formats, no export headaches, no lock-in.
Thanks for reading, and see you in the next one.