Supabase Self-Hosted Installation
Supabase is an open-source Firebase alternative that provides a PostgreSQL database, authentication, real-time subscriptions, file storage, and auto-generated APIs in a single deployable platform. Self-hosting Supabase on your own Linux server gives you full data ownership, no per-row pricing, and the ability to customize every component.
Prerequisites
- A Linux VPS with at least 4 GB RAM (8 GB recommended for production)
- Ubuntu 20.04+ or Debian 11+
- Docker 20.10+ and Docker Compose v2+
- A domain name with DNS configured
- Ports 80 and 443 open
Setting Up Supabase with Docker
# Clone the Supabase self-hosted repository
git clone --depth 1 https://github.com/supabase/supabase
cd supabase/docker
# Copy the example environment file
cp .env.example .env
# Generate required secrets
# JWT Secret (at least 32 characters)
openssl rand -hex 32
# Postgres password
openssl rand -hex 16
# Dashboard password
openssl rand -hex 12
Edit the .env file with your generated values:
# Key values to configure in .env
POSTGRES_PASSWORD=your-super-secret-postgres-password
JWT_SECRET=your-super-secret-jwt-token-at-least-32-characters
ANON_KEY=<generated-anon-jwt>
SERVICE_ROLE_KEY=<generated-service-role-jwt>
DASHBOARD_USERNAME=admin
DASHBOARD_PASSWORD=your-dashboard-password
SITE_URL=https://your-app-domain.com
API_EXTERNAL_URL=https://api.your-domain.com
SUPABASE_PUBLIC_URL=https://api.your-domain.com
Generate JWT tokens using the JWT secret:
# Install jwt-cli or use a script to generate the required JWTs
# The anon and service_role keys are JWTs signed with JWT_SECRET
# Example using Node.js
node -e "
const jwt = require('jsonwebtoken');
const secret = 'your-jwt-secret';
const anon = jwt.sign({role: 'anon', iss: 'supabase'}, secret);
const service = jwt.sign({role: 'service_role', iss: 'supabase'}, secret);
console.log('ANON_KEY=' + anon);
console.log('SERVICE_ROLE_KEY=' + service);
"
Start the Supabase stack:
# Pull all images
docker compose pull
# Start all services in background
docker compose up -d
# Verify all containers are running
docker compose ps
# Expected services:
# supabase-db (PostgreSQL)
# supabase-api (PostgREST)
# supabase-auth (GoTrue)
# supabase-realtime
# supabase-storage
# supabase-studio (dashboard)
# supabase-kong (API gateway)
# supabase-functions
Configuring PostgreSQL
Supabase uses PostgreSQL with several extensions pre-installed:
# Connect to Postgres directly
docker exec -it supabase-db psql -U postgres
# Or using psql with the exposed port
psql "postgresql://postgres:your-password@localhost:5432/postgres"
# Check installed extensions
SELECT * FROM pg_extension;
# Key extensions available:
# pgcrypto - encryption functions
# uuid-ossp - UUID generation
# pgjwt - JWT signing in SQL
# pg_stat_statements - query analytics
Create application tables with Row Level Security:
-- Create a table
CREATE TABLE public.profiles (
id UUID REFERENCES auth.users ON DELETE CASCADE,
username TEXT UNIQUE,
avatar_url TEXT,
updated_at TIMESTAMP WITH TIME ZONE,
PRIMARY KEY (id)
);
-- Enable Row Level Security
ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;
-- Policy: users can only read their own profile
CREATE POLICY "Users can view own profile"
ON public.profiles FOR SELECT
USING (auth.uid() = id);
-- Policy: users can update their own profile
CREATE POLICY "Users can update own profile"
ON public.profiles FOR UPDATE
USING (auth.uid() = id);
Authentication Setup
GoTrue handles authentication in Supabase. Configure providers in .env:
# Email/password auth (enabled by default)
ENABLE_EMAIL_SIGNUP=true
ENABLE_EMAIL_AUTOCONFIRM=false # Set true for development
# Google OAuth
GOTRUE_EXTERNAL_GOOGLE_ENABLED=true
GOTRUE_EXTERNAL_GOOGLE_CLIENT_ID=your-google-client-id
GOTRUE_EXTERNAL_GOOGLE_SECRET=your-google-client-secret
# GitHub OAuth
GOTRUE_EXTERNAL_GITHUB_ENABLED=true
GOTRUE_EXTERNAL_GITHUB_CLIENT_ID=your-github-client-id
GOTRUE_EXTERNAL_GITHUB_SECRET=your-github-client-secret
# SMTP for email delivery
SMTP_HOST=smtp.sendgrid.net
SMTP_PORT=587
SMTP_USER=apikey
SMTP_PASS=your-sendgrid-api-key
[email protected]
SMTP_SENDER_NAME=MyApp
After updating .env, restart auth service:
docker compose restart supabase-auth
Storage Configuration
Supabase Storage manages file uploads with access policies:
# Storage uses local disk by default
# Configure S3-compatible storage in .env:
STORAGE_BACKEND=s3
GLOBAL_S3_BUCKET=your-bucket-name
AWS_DEFAULT_REGION=us-east-1
AWS_ACCESS_KEY_ID=your-access-key
AWS_SECRET_ACCESS_KEY=your-secret-key
# For MinIO (self-hosted S3):
STORAGE_BACKEND=s3
GLOBAL_S3_BUCKET=supabase-storage
AWS_DEFAULT_REGION=us-east-1
AWS_ACCESS_KEY_ID=minioadmin
AWS_SECRET_ACCESS_KEY=minioadmin
STORAGE_S3_ENDPOINT=http://minio:9000
STORAGE_S3_FORCE_PATH_STYLE=true
Create storage buckets via the API:
# Create a public bucket for avatars
curl -X POST "https://api.your-domain.com/storage/v1/bucket" \
-H "Authorization: Bearer your-service-role-key" \
-H "Content-Type: application/json" \
-d '{"id": "avatars", "name": "avatars", "public": true}'
# Create a private bucket for documents
curl -X POST "https://api.your-domain.com/storage/v1/bucket" \
-H "Authorization: Bearer your-service-role-key" \
-H "Content-Type: application/json" \
-d '{"id": "documents", "name": "documents", "public": false}'
Real-Time Subscriptions
Supabase Realtime enables WebSocket subscriptions to database changes:
# Enable replication for a table
# Run in PostgreSQL:
ALTER TABLE public.messages REPLICA IDENTITY FULL;
# Add table to replication publication
ALTER PUBLICATION supabase_realtime ADD TABLE public.messages;
Test real-time with the JavaScript client:
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
'https://api.your-domain.com',
'your-anon-key'
)
// Subscribe to changes
const channel = supabase
.channel('messages')
.on('postgres_changes', {
event: '*',
schema: 'public',
table: 'messages'
}, (payload) => {
console.log('Change received:', payload)
})
.subscribe()
Edge Functions
Supabase Edge Functions run Deno-based serverless functions:
# Install Supabase CLI
npm install -g supabase
# Initialize project
supabase init
# Create a new function
supabase functions new hello-world
# This creates: supabase/functions/hello-world/index.ts
// supabase/functions/hello-world/index.ts
import { serve } from "https://deno.land/[email protected]/http/server.ts"
serve(async (req) => {
const { name } = await req.json()
return new Response(
JSON.stringify({ message: `Hello, ${name}!` }),
{ headers: { "Content-Type": "application/json" } }
)
})
# Deploy function to self-hosted instance
supabase functions deploy hello-world \
--project-ref your-project-ref \
--endpoint https://api.your-domain.com
# Test the function
curl -X POST "https://api.your-domain.com/functions/v1/hello-world" \
-H "Authorization: Bearer your-anon-key" \
-H "Content-Type: application/json" \
-d '{"name": "World"}'
Securing and Exposing Your Instance
Nginx Reverse Proxy
# /etc/nginx/sites-available/supabase
server {
server_name api.your-domain.com;
location / {
proxy_pass http://localhost:8000; # Kong API gateway
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket support for Realtime
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
server {
server_name studio.your-domain.com;
location / {
proxy_pass http://localhost:3000; # Supabase Studio
proxy_set_header Host $host;
}
}
# Enable site and get SSL
sudo ln -s /etc/nginx/sites-available/supabase /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d api.your-domain.com -d studio.your-domain.com
Troubleshooting
Services not starting:
# Check logs for specific service
docker compose logs supabase-auth --tail 50
docker compose logs supabase-db --tail 50
# Check for port conflicts
netstat -tlnp | grep -E "8000|5432|3000"
JWT authentication errors:
# Verify JWT_SECRET matches what was used to generate ANON_KEY and SERVICE_ROLE_KEY
# All three must use the same secret
docker compose exec supabase-kong env | grep JWT
Storage upload fails:
# Check storage service logs
docker compose logs supabase-storage --tail 50
# Verify S3 credentials if using S3 backend
docker compose exec supabase-storage env | grep -i aws
Real-time not receiving events:
# Verify table is in publication
docker compose exec supabase-db psql -U postgres -c "\dRp+"
# Restart realtime service
docker compose restart supabase-realtime
Conclusion
Self-hosted Supabase gives you a complete Firebase alternative with PostgreSQL at its core, delivering authentication, real-time, storage, and auto-generated APIs with full data sovereignty. The Docker Compose setup makes it straightforward to run on any Linux VPS, and the Supabase client libraries work identically with self-hosted and cloud instances. Start with a mid-sized VPS and scale individual services as your application grows.


