Supabase: Got Local Development Finally Working
Firebase was great until I needed to write a complex query. Supabase gave me PostgreSQL, real-time, and auth in one package. Local development was tricky though - took me a week to get it working smoothly. Here's what I learned.
Problem
Tried running supabase start on my MacBook with 8GB RAM.
Got through about 60% of the startup process, then Docker killed the Postgres container.
System monitor showed 95% memory usage before the crash.
Error: Container postgres exited with code 137 (OOM killed)
What I Tried
Attempt 1: Closed all other apps and browser tabs - still crashed.
Attempt 2: Increased Docker memory limit to 6GB - macOS doesn't allow this without hacks.
Attempt 3: Tried using Supabase Cloud instead - worked but wanted local development.
Actual Fix
The default Supabase config starts too many services. I needed to: 1) Disable unnecessary services (like Studio and GoTrue) during development, 2) Reduce Postgres shared_buffers and work_mem, and 3) Use Docker resource limits to prevent runaway memory consumption.
# In supabase/config.toml, disable unused services:
[auth]
enabled = false # Disable GoTrue if not testing auth
[studio]
enabled = false # Disable Studio to save ~500MB RAM
# Create Docker override file to limit resources:
cat > docker-compose.override.yml << EOF
services:
db:
deploy:
resources:
limits:
memory: 2G
command:
- postgres
- -c
- shared_buffers=256MB
- -c
- work_mem=16MB
- -c
- maintenance_work_mem=64MB
EOF
# Or run minimal services only:
supabase start --exclude studio,auth,realtime,imgproxy
# Check actual memory usage:
docker stats --no-stream
# Alternative: Use remote development database
supabase link --project-ref your-project-id
supabase db remote commit
Problem
Created migration files in supabase/migrations directory. Ran supabase db push -
showed "Applied 3 migrations" but the tables didn't exist. Checked the database directly,
the schema_migrations table showed the migrations as applied, but they clearly weren't.
What I Tried
Attempt 1: Dropped and recreated the database - same issue.
Attempt 2: Ran SQL manually in Studio - worked fine.
Attempt 3: Checked migration file timestamps - they were in order.
Actual Fix
The issue was that I was editing migrations that had already been "applied" according to schema_migrations. When you modify an old migration, Supabase doesn't re-run it. The solution: either reset the database or create a new migration that alters the existing schema. Also, migration files must use sequential numbering.
# Don't do this (editing applied migrations):
# supabase/migrations/20240101_initial.sql (already applied)
-- ALTER TABLE users ADD COLUMN new_field text; -- Won't run!
# Instead, create a new migration:
supabase migration new add_new_field
# This creates: supabase/migrations/20240102_add_new_field.sql
# Or reset and replay all migrations:
supabase db reset --linked
# View migration status:
supabase migration list
# Check what migrations have actually run:
supabase db execute --query "SELECT * FROM supabase_migrations.schema_migrations ORDER BY version"
# Best practice: When developing locally
# 1. Make changes in Studio
# 2. Generate migration from those changes:
supabase db remote commit
# 3. Apply to local:
supabase db reset
Problem
Enabled RLS on a table, created a policy to allow users to see their own data. Querying from the client returned an empty array every time. Verified the auth.uid() was correct, but the policy seemed to block everything, even rows I owned.
What I Tried
Attempt 1: Simplified the policy to just "auth.uid() = user_id" - still blocked.
Attempt 2: Disabled RLS temporarily - data appeared, confirming the policy was the issue.
Attempt 3: Used USING vs IN check - neither worked.
Actual Fix
The issue was subtle: my policy used USING (for SELECT) but I also needed WITH CHECK (for INSERT/UPDATE). Also, the user_id column in my table was UUID but I was comparing it incorrectly. Needed explicit casting and to check both USING and WITH CHECK for full CRUD operations.
What I Learned
- Lesson 1: Local Supabase needs serious RAM - disable Studio and Auth when not needed, or use cloud development database.
- Lesson 2: Never edit applied migrations - create new ones or use
supabase db reset. - Lesson 3: RLS policies need both USING (read) and WITH CHECK (write) - one without the other causes silent failures.
- Overall: Supabase vs Firebase: Supabase gives you raw SQL power which is amazing for complex queries, but Firebase is simpler for basic apps. If you know SQL, Supabase is a game-changer.
Production Setup
Local development setup that doesn't kill your machine.
# 1. Install Supabase CLI
brew install supabase/tap/supabase
# 2. Initialize project
supabase init
# 3. Configure for low-RAM development
cat > supabase/config.toml << EOF
[project_id]
[api]
enabled = true
port = 54321
schemas = ["public", "graphql_public"]
[db]
port = 54322
shadow_db_port = 54320
major_version = "15.1.0"
[studio]
enabled = false # Disable to save RAM
port = 54323
[inbucket]
enabled = false # Disable email testing
port = 54324
[auth]
enabled = false # Disable if not testing auth
site_url = "http://localhost:3000"
[edge_runtime]
enabled = false # Disable if not using Edge Functions
policy = "per_request"
[cli]
smart_zip = true
EOF
# 4. Start minimal stack
supabase start --exclude studio,auth,inbucket,realtime,imgproxy
# 5. Or link to cloud for development
supabase link --project-ref your-project-id
supabase db remote commit
// Client initialization
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = process.env.SUPABASE_URL
const supabaseKey = process.env.SUPABASE_ANON_KEY
const supabase = createClient(supabaseUrl, supabaseKey)
// Query with RLS
const { data, error } = await supabase
.from('posts')
.select('*')
// RLS automatically filters by user_id
// Real-time subscription
const channel = supabase
.channel('custom-channel')
.on('postgres_changes', {
event: 'INSERT',
schema: 'public',
table: 'posts'
}, (payload) => console.log('New post!', payload.new))
.subscribe()
Monitoring & Debugging
Essential Supabase development commands:
Essential Commands
# Start local development
supabase start
# Stop all services
supabase stop
# View logs
supabase status
supabase logs
# Database operations
supabase db reset # Reset local database
supabase db push # Push migrations to local
supabase db remote commit # Capture remote schema
# Generate typescript types
supabase gen types typescript --local > lib/supabase.types.ts
# Test a query
supabase db execute --query "SELECT COUNT(*) FROM users"
# Open Studio UI (if enabled)
supabase inspect
Supabase vs Firebase
| Feature | Supabase | Firebase |
|---|---|---|
| Database | PostgreSQL (SQL) | Firestore (NoSQL) |
| Query Complexity | Full SQL power | Limited query language |
| Real-time | PostgreSQL changes | Native listeners |
| Local Development | Full local stack | Emulator suite |
| Learning Curve | Need SQL knowledge | Easier for beginners |