DEV Community

Royce
Royce

Posted on • Originally published at ossalt.com

Self-Hosting Guide: Deploy Supabase Locally

Self-Hosting Guide: Deploy Supabase Locally

Supabase is the open source Firebase alternative — PostgreSQL database, authentication, storage, edge functions, and real-time subscriptions. Self-hosting removes the 500 MB database limit, gives you unlimited API requests, and costs a fraction of Supabase Cloud's $25/month Pro plan.

Requirements

  • VPS with 4 GB RAM minimum (8 GB recommended)
  • Docker and Docker Compose
  • Domain name (e.g., supabase.yourdomain.com)
  • 30+ GB disk

Step 1: Clone Supabase Docker

git clone --depth 1 https://github.com/supabase/supabase.git
cd supabase/docker

# Copy environment
cp .env.example .env
Enter fullscreen mode Exit fullscreen mode

Step 2: Generate Secrets

# Generate JWT secret
openssl rand -hex 32

# Generate anon key and service role key using the JWT secret
# Use https://supabase.com/docs/guides/self-hosting/docker#generate-api-keys
# Or generate manually with jwt.io
Enter fullscreen mode Exit fullscreen mode

Step 3: Configure Environment

Edit .env:

# General
SITE_URL=https://supabase.yourdomain.com
API_EXTERNAL_URL=https://supabase.yourdomain.com

# JWT
JWT_SECRET=your-jwt-secret-min-32-chars
ANON_KEY=your-generated-anon-key
SERVICE_ROLE_KEY=your-generated-service-role-key

# Database
POSTGRES_PASSWORD=your-strong-password
POSTGRES_HOST=db
POSTGRES_DB=postgres
POSTGRES_PORT=5432

# Dashboard
DASHBOARD_USERNAME=admin
DASHBOARD_PASSWORD=your-dashboard-password

# SMTP
SMTP_HOST=smtp.resend.com
SMTP_PORT=587
SMTP_USER=resend
SMTP_PASS=re_your_api_key
SMTP_SENDER_NAME=Supabase
SMTP_ADMIN_EMAIL=admin@yourdomain.com

# Storage
STORAGE_BACKEND=file
FILE_SIZE_LIMIT=52428800
Enter fullscreen mode Exit fullscreen mode

Step 4: Start Supabase

docker compose up -d
Enter fullscreen mode Exit fullscreen mode

This starts all Supabase services:

Service Port Purpose
Kong (API Gateway) 8000 API routing
GoTrue 9999 Authentication
PostgREST 3000 REST API from PostgreSQL
Realtime 4000 WebSocket subscriptions
Storage 5000 File storage API
Studio 3001 Dashboard UI
PostgreSQL 5432 Database
Meta 8080 Metadata API

Step 5: Reverse Proxy (Caddy)

# /etc/caddy/Caddyfile
supabase.yourdomain.com {
    # API Gateway
    reverse_proxy localhost:8000
}

studio.yourdomain.com {
    # Dashboard
    reverse_proxy localhost:3001
}
Enter fullscreen mode Exit fullscreen mode
sudo systemctl restart caddy
Enter fullscreen mode Exit fullscreen mode

Step 6: Access Dashboard

  1. Open https://studio.yourdomain.com
  2. Login with your dashboard credentials
  3. Create your first project

Step 7: Connect Your App


const supabase = createClient(
  'https://supabase.yourdomain.com',
  'your-anon-key'
)

// Query data
const { data, error } = await supabase
  .from('todos')
  .select('*')

// Insert data
const { data, error } = await supabase
  .from('todos')
  .insert({ title: 'My todo', completed: false })

// Auth
const { data, error } = await supabase.auth.signUp({
  email: 'user@example.com',
  password: 'password123',
})

// Storage
const { data, error } = await supabase.storage
  .from('avatars')
  .upload('user1/avatar.png', file)

// Realtime
supabase
  .channel('todos')
  .on('postgres_changes', { event: '*', schema: 'public', table: 'todos' },
    (payload) => console.log('Change:', payload)
  )
  .subscribe()
Enter fullscreen mode Exit fullscreen mode

Step 8: Set Up Row Level Security

Enable RLS on your tables for production security:

-- Enable RLS
ALTER TABLE todos ENABLE ROW LEVEL SECURITY;

-- 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);
Enter fullscreen mode Exit fullscreen mode

Production Hardening

Restrict Studio access:

  • Put Studio behind VPN or IP allowlist
  • Use strong dashboard credentials
  • Consider disabling Studio in production

Backups:

# Database backup (daily cron)
docker exec supabase-db pg_dump -U postgres postgres > /backups/supabase-$(date +%Y%m%d).sql

# Storage backup
tar czf /backups/supabase-storage-$(date +%Y%m%d).tar.gz ./volumes/storage
Enter fullscreen mode Exit fullscreen mode

Updates:

cd supabase/docker
git pull
docker compose pull
docker compose up -d
Enter fullscreen mode Exit fullscreen mode

Monitoring:

  • Monitor Kong API gateway (port 8000)
  • Monitor PostgreSQL connections and query performance
  • Set up disk space alerts (database grows)
  • Monitor GoTrue for auth failures

Resource Usage

Scale RAM CPU Disk
Development 4 GB 2 cores 20 GB
Small app (1K users) 8 GB 4 cores 50 GB
Medium app (10K users) 16 GB 8 cores 100 GB

VPS Recommendations

Provider Spec (small app) Price
Hetzner 4 vCPU, 8 GB RAM €8/month
DigitalOcean 4 vCPU, 8 GB RAM $48/month
Linode 4 vCPU, 8 GB RAM $48/month

Supabase is resource-intensive due to multiple services. Use Hetzner for the best price-performance ratio.


Compare backend platforms on OSSAlt — features, pricing, and self-hosting options side by side.

Top comments (0)