Skip to content

Supabase Self-Hosted Stack Documentation

Table of Contents

  1. Overview
  2. Architecture
  3. Why Supabase
  4. Configuration Details
  5. Network Architecture
  6. Bootstrap and Initialization
  7. JWT Keys and Authentication
  8. Backup Strategy
  9. Common Operations
  10. Troubleshooting Guide
  11. Comparison with Hosted Supabase
  12. When to Use Supabase vs Pocketbase

Overview

Supabase is an open-source Backend-as-a-Service (BaaS) platform that provides a complete backend infrastructure for modern applications. It positions itself as an open-source alternative to Firebase, built on top of enterprise-grade open-source tools, primarily PostgreSQL.

In this infrastructure, Supabase is deployed as a self-hosted solution on Docker, providing: - PostgreSQL database with advanced extensions - Authentication service (GoTrue) - Real-time subscriptions (via WebSocket and PostgreSQL's logical replication) - Auto-generated REST APIs (PostgREST) - Object storage (S3-compatible) - Database management UI (Supabase Studio) - Edge Functions (Deno runtime for serverless functions) - Image transformations (Imgproxy for on-the-fly image optimization) - Log analytics (Logflare/Analytics for centralized logging) - Log aggregation (Vector for log collection)

The stack is exposed via the domain supabase.rbnk.uk with SSL termination handled by Traefik reverse proxy.

Architecture

The Supabase stack consists of 11 interconnected containers, each serving a specific purpose:

1. supa-db (PostgreSQL Database)

  • Image: supabase/postgres:15.8.1.085
  • Role: Core database with Supabase-specific extensions and configurations
  • Internal Port: 5432 (not exposed externally)
  • Features:
  • PostgreSQL 15.8 with extensions: pgvector, pg_stat_statements, pgaudit, pgjwt, pgsodium, pg_graphql
  • Custom schemas: auth, storage, realtime, extensions
  • Row Level Security (RLS) enabled by default
  • Logical replication for real-time features

2. supa-auth (Authentication Service)

  • Image: supabase/gotrue:v2.183.0
  • Role: Handles user authentication, registration, and session management
  • Port: 8081 (internal), exposed as /auth/v1/*
  • Features:
  • JWT-based authentication
  • OAuth provider integrations
  • Email/password authentication
  • Magic link authentication
  • Multi-factor authentication support

3. supa-rest (REST API)

  • Image: postgrest/postgrest:v13.0.7
  • Role: Auto-generates RESTful APIs from PostgreSQL schemas
  • Port: 3000 (internal), exposed as /rest/v1/*
  • Features:
  • Instant CRUD APIs from database tables
  • Respects PostgreSQL's Row Level Security
  • Support for stored procedures as RPC endpoints
  • GraphQL-like filtering and embedding

4. supa-storage (Object Storage)

  • Image: supabase/storage-api:v1.32.0
  • Role: S3-compatible object storage for files and media
  • Port: 5000 (internal), exposed as /storage/v1/*
  • Features:
  • File upload/download with access control
  • Image transformations on-the-fly
  • Integration with PostgreSQL for metadata
  • Bucket-based organization

5. supa-studio (Management UI)

  • Image: supabase/studio:2025.11.26-sha-8f096b5
  • Role: Web-based database management interface
  • Port: 3000 (internal), exposed as root path
  • Features:
  • Visual table editor
  • SQL query runner
  • User management
  • API documentation
  • Real-time logs viewer

6. supa-meta (Database Introspection)

  • Image: supabase/postgres-meta:v0.93.1
  • Role: Provides database metadata and introspection APIs
  • Port: 8080 (internal only)
  • Features:
  • Schema introspection
  • Table relationships mapping
  • Database statistics
  • Used by Studio for UI generation

7. supa-realtime (Real-time WebSocket Service)

  • Image: supabase/realtime:v2.65.3
  • Role: Enables real-time subscriptions for database changes
  • Port: 4000 (internal), exposed as /realtime/v1/*
  • Features:
  • WebSocket-based real-time subscriptions
  • Postgres CDC (Change Data Capture) via logical replication
  • Channel-based pub/sub messaging
  • Row Level Security enforcement
  • Presence tracking for user activity

8. supa-analytics (Log Analytics - Logflare)

  • Image: supabase/logflare:1.26.13
  • Role: Centralized log analytics and monitoring
  • Port: 4000 (internal), exposed as /analytics/v1/*
  • Features:
  • Real-time log ingestion and querying
  • Structured log storage in PostgreSQL
  • Integration with Vector for log collection
  • Multi-tenant log isolation
  • Query interface for log analysis

9. supa-vector (Log Aggregation)

  • Image: timberio/vector:0.28.1-alpine
  • Role: Collects and routes logs from all Supabase containers
  • Port: 9001 (health check only)
  • Features:
  • Docker log collection from all supa-* containers
  • Log filtering and transformation
  • Batched delivery to Logflare
  • Low-latency, high-throughput processing

10. supa-functions (Edge Functions)

  • Image: supabase/edge-runtime:v1.69.25
  • Role: Serverless function execution with Deno runtime
  • Port: 9000 (internal), exposed as /functions/v1/*
  • Features:
  • Deno-based serverless functions
  • TypeScript/JavaScript support
  • Direct database access via Supabase client
  • JWT authentication integration
  • Hot reloading for development

11. supa-imgproxy (Image Transformations)

  • Image: darthsim/imgproxy:v3.8.0
  • Role: On-the-fly image resizing and optimization
  • Port: 5001 (internal)
  • Features:
  • Dynamic image resizing and cropping
  • Format conversion (WebP, AVIF, etc.)
  • ETag support for caching
  • Integration with storage service
  • Memory-efficient processing

Why Supabase

Supabase was chosen for this infrastructure for several compelling reasons:

1. PostgreSQL Foundation

  • Enterprise-grade database with 30+ years of development
  • ACID compliance and strong consistency guarantees
  • Advanced features: JSON, full-text search, geospatial data
  • Extensibility through custom functions and extensions

2. Comprehensive Feature Set

  • Authentication, database, storage, and real-time in one platform
  • Reduces the need for multiple services and complex integrations
  • Consistent security model across all components

3. Developer Experience

  • Auto-generated APIs eliminate boilerplate code
  • Type-safe client libraries for popular frameworks
  • Row Level Security provides fine-grained access control
  • Real-time subscriptions out of the box

4. Open Source

  • No vendor lock-in
  • Self-hostable with full control over data
  • Active community and transparent development
  • Can contribute fixes or features if needed

5. Production Ready

  • Used by thousands of production applications
  • Regular security updates and patches
  • Comprehensive documentation and examples
  • Enterprise support available if needed

Configuration Details

PostgreSQL Setup

The PostgreSQL instance is configured with:

services:
  db:
    image: ghcr.io/supabase/postgres:15.8.1.060
    volumes:
      - ./data/db:/var/lib/postgresql/data
      - ./bootstrap-sql/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d:ro

Key PostgreSQL Extensions: - uuid-ossp: UUID generation - pgcrypto: Cryptographic functions - pgjwt: JWT generation/validation - pg_stat_statements: Query performance monitoring - pgaudit: Audit logging - pgsodium: Encryption library - pg_graphql: GraphQL support - pgvector: Vector similarity search

Auth Service (GoTrue) Configuration

environment:
  GOTRUE_SITE_URL: https://supabase.rbnk.uk
  GOTRUE_DB_DATABASE_URL: postgres://postgres:${POSTGRES_PASSWORD}@db:5432/postgres
  GOTRUE_DB_NAMESPACE: auth
  GOTRUE_JWT_SECRET: ${JWT_SECRET}
  GOTRUE_MAILER_AUTOCONFIRM: true  # Dev setting

The auth service manages: - User registration and login - JWT token generation and validation - Password resets and email verification - OAuth provider integrations

PostgREST API Setup

environment:
  PGRST_DB_URI: postgres://postgres:${POSTGRES_PASSWORD}@db:5432/postgres
  PGRST_DB_ANON_ROLE: anon
  PGRST_DB_SCHEMAS: public
  PGRST_JWT_SECRET: ${JWT_SECRET}

PostgREST configuration: - Exposes public schema by default - Uses anon role for unauthenticated requests - Automatically switches to authenticated role for logged-in users - Respects Row Level Security policies

Storage Configuration

environment:
  STORAGE_BACKEND: file
  FILE_STORAGE_BACKEND_PATH: /var/lib/storage
  STORAGE_DATABASE_URL: postgres://postgres:${POSTGRES_PASSWORD}@db:5432/postgres

Storage setup: - File-based backend (can be switched to S3) - Metadata stored in PostgreSQL - Access control via RLS policies - Automatic image optimization

Studio Access

Studio is configured as the default route:

labels:
  - traefik.http.routers.supa-studio.rule=Host(`supabase.rbnk.uk`)

This provides easy access to the management interface at the root domain.

Network Architecture

The Supabase stack implements a dual-network architecture for enhanced security:

1. Internal Network (supabase_internal)

  • Bridge network for inter-service communication
  • Database is only accessible on this network
  • Services communicate using container names as hostnames
  • Isolated from external access

2. External Network (traefik_proxy)

  • Shared with Traefik reverse proxy
  • Only services that need external access join this network
  • SSL termination happens at Traefik level
  • Provides unified access point
networks:
  internal:
    name: supabase_internal
    driver: bridge
  traefik_proxy:
    external: true

Security Benefits: - Database never exposed to external network - Service-to-service communication stays internal - Single entry point through Traefik - Easy to implement firewall rules

Service Network Membership: - supa-db: internal only (maximum security) - supa-auth: internal only (accessed via Kong) - supa-rest: internal only (accessed via Kong) - supa-storage: internal only (accessed via Kong) - supa-studio: internal only (accessed via Kong) - supa-meta: internal only - supa-realtime: internal only (accessed via Kong) - supa-functions: internal only (accessed via Kong) - supa-analytics: internal only (accessed via Kong) - supa-vector: internal only (log collection) - supa-imgproxy: internal only (used by storage) - supa-kong: internal + traefik_proxy (API gateway)

Bootstrap and Initialization

The PostgreSQL database is initialized with a comprehensive set of bootstrap scripts located in /docker-entrypoint-initdb.d/:

Initialization Order

  1. 000-create-supabase-admin.sh: Creates initial admin user
  2. 00-schema.sql: PgBouncer setup
  3. 00000000000000-initial-schema.sql: Core Supabase schemas
  4. 00000000000001-auth-schema.sql: Authentication tables
  5. 00000000000002-storage-schema.sql: Storage tables
  6. 00000000000003-post-setup.sql: Final configurations
  7. migrations/*.sql: Incremental updates (sorted by timestamp)

Key Bootstrap Components

Roles Created: - postgres: Superuser (existing) - supabase_admin: Administrative operations - supabase_auth_admin: Auth service operations - supabase_storage_admin: Storage service operations - anon: Anonymous/public access - authenticated: Logged-in users - service_role: Backend service access - authenticator: PostgREST connection role

Schemas Created: - auth: Authentication tables and functions - storage: File storage metadata - realtime: Real-time subscription management - extensions: PostgreSQL extensions - public: Application tables (user-defined)

Critical Tables: - auth.users: User accounts - auth.refresh_tokens: Session management - storage.buckets: Storage containers - storage.objects: File metadata

Initialization Process

  1. Container checks if data/db is empty
  2. If empty, runs initdb to create cluster
  3. Executes all scripts in /docker-entrypoint-initdb.d/
  4. Scripts run in lexicographical order
  5. Each script is executed as superuser
  6. Process is idempotent (can be re-run safely)

JWT Keys and Authentication

Supabase uses JWT (JSON Web Tokens) for authentication across all services:

Key Types

  1. anon key: Public key for unauthenticated access
  2. Used in client-side applications
  3. Has minimal permissions (defined by RLS)
  4. Safe to expose in frontend code

  5. service_role key: Private key for backend services

  6. Bypasses Row Level Security
  7. Full database access
  8. Must be kept secret

JWT Configuration

# Shared secret for signing/verifying JWTs
JWT_SECRET=your-32-byte-secret-key

# Keys are JWTs signed with the secret containing role claims
ANON_KEY=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...
SERVICE_ROLE_KEY=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...

Authentication Flow

  1. User Login:
  2. Client sends credentials to /auth/v1/token
  3. GoTrue validates and returns access token
  4. Token contains user ID and role

  5. API Request:

  6. Client includes token in Authorization header
  7. PostgREST validates token signature
  8. Database session uses token's role
  9. RLS policies enforce access control

  10. Token Refresh:

  11. Access tokens expire (default: 1 hour)
  12. Refresh token used to get new access token
  13. Maintains user session without re-login

Security Considerations

  • JWT secret must be at least 32 characters
  • Rotate keys periodically in production
  • Use HTTPS for all API calls
  • Implement token refresh logic in clients
  • Monitor for suspicious authentication patterns

Backup Strategy

The backup system is automated via systemd timer (3 AM daily) with the following components:

Backup Script (/srv/dockerdata/_scripts/supabase-backup.sh)

What's Backed Up: 1. Database: Complete logical dump via pg_dumpall - All databases, schemas, and data - Roles and permissions - Compressed with gzip -9

  1. Configuration:
  2. docker-compose.yml files
  3. Environment files (.env)
  4. Bootstrap SQL scripts
  5. Traefik configuration

Backup Location: /srv/backups/supabase/ - db/: Database dumps - config/: Configuration archives

Retention: 14 days (configurable)

Backup Process

# Daily automated backup
sudo systemctl status supabase-backup.timer

# Manual backup
sudo /srv/dockerdata/_scripts/supabase-backup.sh

Restore Process

  1. Stop services: docker compose down
  2. Restore database:
    gunzip -c /srv/backups/supabase/db/pgdumpall-TIMESTAMP.sql.gz | \
      docker compose exec -T db psql -U postgres
    
  3. Restore config: Extract config archive to appropriate locations
  4. Start services: docker compose up -d

Backup Best Practices

  • Test restore procedure regularly
  • Store backups on different storage/server
  • Monitor backup job success/failure
  • Consider point-in-time recovery for production
  • Encrypt backups containing sensitive data

Common Operations

Database Access

# Direct PostgreSQL access
docker exec -it supa-db psql -U postgres

# Common database commands
\l                    # List databases
\c postgres           # Connect to database
\dn                   # List schemas
\dt public.*          # List tables in public schema
\du                   # List roles

User Management

-- View all users
SELECT * FROM auth.users;

-- Create admin user (via SQL)
INSERT INTO auth.users (id, email, encrypted_password, email_confirmed_at, role)
VALUES (
  gen_random_uuid(),
  '[email protected]',
  crypt('password123', gen_salt('bf')),
  now(),
  'authenticated'
);

-- Grant admin privileges
UPDATE auth.users 
SET raw_app_meta_data = '{"role": "admin"}'::jsonb 
WHERE email = '[email protected]';

API Usage

# Test anonymous access
curl https://supabase.rbnk.uk/rest/v1/

# Query with anon key
curl https://supabase.rbnk.uk/rest/v1/your_table \
  -H "apikey: YOUR_ANON_KEY" \
  -H "Authorization: Bearer YOUR_ANON_KEY"

# Insert with service role key (bypasses RLS)
curl -X POST https://supabase.rbnk.uk/rest/v1/your_table \
  -H "apikey: YOUR_SERVICE_KEY" \
  -H "Authorization: Bearer YOUR_SERVICE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"column": "value"}'

Service Management

# View service status
docker compose ps

# Restart specific service
docker compose restart supa-auth

# View logs
docker compose logs -f supa-storage

# Scale services (if stateless)
docker compose up -d --scale supa-rest=2

Database Migrations

-- Create migration
CREATE TABLE IF NOT EXISTS public.migrations (
  id serial PRIMARY KEY,
  name text NOT NULL,
  executed_at timestamptz DEFAULT now()
);

-- Apply migration
BEGIN;
  -- Your schema changes here
  INSERT INTO migrations (name) VALUES ('add_user_profiles');
COMMIT;

Troubleshooting Guide

Common Issues

1. Auth Schema Does Not Exist

Symptoms: Services fail with "schema auth does not exist" Cause: Bootstrap SQL didn't run during initialization Fix:

# Ensure bootstrap scripts are mounted
docker compose down
sudo rm -rf data/db
docker compose up -d db
# Check logs for script execution
docker logs supa-db | grep "running /docker-entrypoint-initdb.d"

2. Role 'anon' Does Not Exist

Symptoms: PostgREST fails to start Cause: Database roles not created Fix: Re-run initialization or manually create roles

3. Storage Health Check Failing

Symptoms: Traefik shows storage as unhealthy Cause: Health endpoint path mismatch Fix: Already configured with custom health check rewrite in Traefik

4. JWT Signature Invalid

Symptoms: 401 errors on API calls Cause: Mismatched JWT secrets between services Fix: Ensure all services use same JWT_SECRET value

5. Cannot Connect to Database

Symptoms: Services can't reach PostgreSQL Cause: Network configuration or credentials Fix: - Verify service is on internal network - Check connection string uses db as hostname - Verify PostgreSQL is running

Debug Commands

# Check network connectivity
docker compose exec supa-auth ping db

# Test database connection
docker compose exec supa-auth psql $GOTRUE_DATABASE_URL -c "SELECT 1"

# Verify JWT secret
docker compose exec supa-rest env | grep JWT_SECRET

# Check file permissions
docker compose exec db ls -la /var/lib/postgresql/data

# Inspect bootstrap scripts
docker compose exec db ls -la /docker-entrypoint-initdb.d/

Log Analysis

# Check all logs
docker compose logs

# Service-specific logs with timestamps
docker compose logs -t supa-auth

# Follow logs in real-time
docker compose logs -f

# Search for errors
docker compose logs | grep -i error

Comparison with Hosted Supabase

Self-Hosted Advantages

  1. Data Sovereignty: Complete control over data location
  2. Customization: Modify any component or configuration
  3. Cost Predictability: No per-request pricing, only infrastructure costs
  4. Network Control: Can run in air-gapped environments
  5. Compliance: Easier to meet specific regulatory requirements

Self-Hosted Disadvantages

  1. Maintenance Burden: You handle updates, backups, monitoring
  2. No Automatic Scaling: Manual intervention for scaling
  3. Missing Features: Some cloud-only features (edge functions, vector embeddings)
  4. Security Responsibility: You manage all security patches
  5. No Global CDN: Must implement your own edge caching

Feature Comparison

Feature Hosted Self-Hosted
Automatic backups ❌ (manual setup)
Global CDN
Edge Functions ✅ (Deno runtime)
Real-time subscriptions
Log analytics ✅ (Logflare)
Image transformations ✅ (Imgproxy)
Automatic updates
Custom domains
Full SQL access
Custom extensions
Air-gapped deploy

When to Use Supabase vs Pocketbase

Choose Supabase When:

  1. PostgreSQL Required:
  2. Need advanced SQL features
  3. Complex queries and joins
  4. Existing PostgreSQL expertise
  5. Need specific PostgreSQL extensions

  6. Scale Matters:

  7. Expecting millions of records
  8. High concurrent users
  9. Complex access patterns
  10. Need read replicas

  11. Feature Requirements:

  12. Real-time subscriptions
  13. Row Level Security
  14. Built-in auth with OAuth
  15. File storage integration

  16. Team Structure:

  17. Multiple developers
  18. Dedicated backend team
  19. Need role-based access

Choose Pocketbase When:

  1. Simplicity First:
  2. Single binary deployment
  3. Minimal configuration
  4. Quick prototypes
  5. Learning projects

  6. Resource Constraints:

  7. Limited RAM (< 1GB)
  8. Shared hosting
  9. Edge devices
  10. Embedded scenarios

  11. Use Case:

  12. Admin panels
  13. Internal tools
  14. Simple CRUD apps
  15. Single-user applications

  16. Development Speed:

  17. Need working backend in minutes
  18. No infrastructure expertise
  19. Minimal dependencies

Comparison Matrix

Aspect Supabase Pocketbase
Database PostgreSQL SQLite
Deployment 11+ containers Single binary
Memory usage 4GB+ 10-50MB
Real-time Native WebSocket WebSocket (basic)
Auth providers 20+ 5+
File storage Integrated + Imgproxy Integrated
Admin UI Full Studio Basic
API REST + GraphQL REST only
Edge Functions Deno runtime None
Log Analytics Logflare None
Migrations SQL based Go based
Extensions 50+ available None
Horizontal scale Yes No
Backup pg_dump File copy

Migration Path

Pocketbase → Supabase: - Export data as JSON/CSV - Create PostgreSQL schema - Import data with transformations - Update client to use Supabase SDK

Supabase → Pocketbase: - Generally not recommended due to feature loss - Possible for simple schemas - Requires significant refactoring

Conclusion

The self-hosted Supabase stack provides a production-ready, open-source backend infrastructure with the power of PostgreSQL at its core. The architecture's separation of concerns, comprehensive security model, and extensive feature set make it suitable for applications ranging from startups to enterprise deployments.

Key takeaways: - Robust 11-container architecture with clear separation of concerns - Kong API gateway provides unified routing and authentication - Dual-network design enhances security - JWT-based authentication provides flexible access control - Edge Functions enable serverless workloads with Deno runtime - Real-time subscriptions via WebSocket for live database changes - Centralized log analytics with Logflare and Vector - Automated backup strategy ensures data safety - Comprehensive bootstrap process handles complex initialization - Choose based on your specific needs: Supabase for feature-rich applications, Pocketbase for simplicity

For production deployments, ensure proper secret rotation, monitoring, and backup testing. The self-hosted nature provides ultimate control but requires operational expertise to maintain effectively.