Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Micro-Supabase

A lightweight, self-contained Supabase-compatible backend in a single Docker container. Perfect for local development, testing, and learning.

What is Micro-Supabase?

Micro-Supabase packages the core Supabase services into one Docker image (~337MB):

  • pglited - PostgreSQL-compatible in-memory database
  • GoTrue v2.186.0 - Authentication service with JWT support
  • PostgREST v14.4 - Auto-generated REST API from your database schema
  • Nginx - Reverse proxy providing a single Supabase-js compatible endpoint

Note: Currently built for ARM64 architecture (Apple Silicon, AWS Graviton).

Features

  • ✅ Full Supabase authentication (signup, signin, JWT tokens)
  • ✅ Auto-generated REST API from database schema
  • ✅ Row Level Security (RLS) policies
  • ✅ Automatic migration execution on startup
  • ✅ Supabase-js client compatible
  • ✅ Configurable ports
  • ✅ Single command to run

Quick Start

1. Build the Docker Image

./build.sh

2. Run the Container

In-memory mode (default, data lost on restart):

docker run -d --name micro-supabase \
  -p 54321:54321 \
  -p 8080:8080 \
  -v "$(pwd)/migrations:/app/migrations" \
  micro-supabase:latest

Persistent mode (data retained across restarts):

docker run -d --name micro-supabase \
  -p 54321:54321 \
  -p 8080:8080 \
  -e STORAGE_MODE=disk \
  -v "$(pwd)/migrations:/app/migrations" \
  -v "$(pwd)/data:/app/data" \
  micro-supabase:latest

Note: Mount your migrations directory with -v "$(pwd)/migrations:/app/migrations" to run migrations on startup.

3. Test the API

Check health:

curl http://0.0.0.0:8080/auth/v1/health

Sign up a user:

curl -X POST http://0.0.0.0:8080/auth/v1/signup \
  -H "Content-Type: application/json" \
  -H "apikey: super-secret-jwt-token-with-at-least-32-characters-long" \
  -d '{"email":"user@example.com","password":"password123"}'

Sign in:

curl -X POST "http://0.0.0.0:8080/auth/v1/token?grant_type=password" \
  -H "Content-Type: application/json" \
  -H "apikey: super-secret-jwt-token-with-at-least-32-characters-long" \
  -d '{"email":"user@example.com","password":"password123"}'

Configuration

Environment Variables

Configure ports and storage mode by passing environment variables:

docker run -d --name micro-supabase \
  -p 55432:54321 \
  -p 9090:8080 \
  -e STORAGE_MODE=disk \
  -e PG_PORT=54321 \
  -e AUTH_PORT=9999 \
  -e POSTGREST_PORT=3000 \
  -e PROXY_PORT=8080 \
  -v "$(pwd)/migrations:/app/migrations" \
  -v "$(pwd)/data:/app/data" \
  micro-supabase:latest

Available variables:

  • STORAGE_MODE - Storage mode: memory (default, ephemeral) or disk (persistent)
  • PG_PORT - PostgreSQL port (default: 54321)
  • AUTH_PORT - Auth service port (default: 9999)
  • POSTGREST_PORT - PostgREST port (default: 3000)
  • PROXY_PORT - Nginx proxy port (default: 8080)

Storage Modes:

  • memory (default): Data stored in memory, lost when container stops. Fast, ideal for testing.
  • disk: Data persisted to /app/data/pglite directory. Requires volume mount: -v "$(pwd)/data:/app/data"

Exposed Ports

Only two ports are exposed to the host:

  • 54321 - PostgreSQL direct access
  • 8080 - Single Supabase-js endpoint (Auth + REST API)

Migrations

Adding Your Own Migrations

Place SQL migration files in a directory and mount it to /app/migrations:

docker run -d --name micro-supabase \
  -p 54321:54321 \
  -p 8080:8080 \
  -v "$(pwd)/migrations:/app/migrations" \
  micro-supabase:latest

Migration example (001_create_posts.sql):

-- Create posts table
CREATE TABLE public.posts (
  id BIGSERIAL PRIMARY KEY,
  user_id UUID NOT NULL,
  title TEXT NOT NULL,
  content TEXT,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

-- Enable RLS
ALTER TABLE public.posts ENABLE ROW LEVEL SECURITY;

-- RLS policies using auth.uid()
CREATE POLICY "Users can view their own posts" ON public.posts
  FOR SELECT
  USING (auth.uid() = user_id);

CREATE POLICY "Users can create their own posts" ON public.posts
  FOR INSERT
  WITH CHECK (auth.uid() = user_id);

-- Grant permissions
GRANT ALL ON TABLE public.posts TO authenticated;
GRANT SELECT ON TABLE public.posts TO anon;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO authenticated;

Migrations run automatically on container startup in alphabetical order.

Using with Supabase-js Client

import { createClient } from '@supabase/supabase-js'

const supabase = createClient(
  'http://0.0.0.0:8080',
  'super-secret-jwt-token-with-at-least-32-characters-long'
)

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

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

// Query data (with automatic RLS)
const { data: posts } = await supabase
  .from('posts')
  .select('*')

API Examples

Authentication

Signup:

curl -X POST http://0.0.0.0:8080/auth/v1/signup \
  -H "Content-Type: application/json" \
  -H "apikey: super-secret-jwt-token-with-at-least-32-characters-long" \
  -d '{"email":"user@example.com","password":"password123"}'

Signin:

curl -X POST "http://0.0.0.0:8080/auth/v1/token?grant_type=password" \
  -H "Content-Type: application/json" \
  -H "apikey: super-secret-jwt-token-with-at-least-32-characters-long" \
  -d '{"email":"user@example.com","password":"password123"}'

Get user:

curl http://0.0.0.0:8080/auth/v1/user \
  -H "apikey: super-secret-jwt-token-with-at-least-32-characters-long" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

REST API (PostgREST)

List all items:

curl http://0.0.0.0:8080/rest/v1/todos \
  -H "apikey: super-secret-jwt-token-with-at-least-32-characters-long" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

Create item:

curl -X POST http://0.0.0.0:8080/rest/v1/todos \
  -H "Content-Type: application/json" \
  -H "apikey: super-secret-jwt-token-with-at-least-32-characters-long" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Prefer: return=representation" \
  -d '{"user_id":"USER_ID","title":"My todo","completed":false}'

Update item:

curl -X PATCH "http://0.0.0.0:8080/rest/v1/todos?id=eq.1" \
  -H "Content-Type: application/json" \
  -H "apikey: super-secret-jwt-token-with-at-least-32-characters-long" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Prefer: return=representation" \
  -d '{"completed":true}'

Delete item:

curl -X DELETE "http://0.0.0.0:8080/rest/v1/todos?id=eq.1" \
  -H "apikey: super-secret-jwt-token-with-at-least-32-characters-long" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

Demo React Application

A demo React + TypeScript app is included in the demo/ folder.

cd demo
npm install
npm run dev

The demo shows:

  • Sign up / Sign in / Sign out
  • Todo CRUD operations
  • Row Level Security in action
  • Session persistence

PostgreSQL Direct Access

Connect directly to the database:

# Using psql from host
psql "postgresql://postgres:password@127.0.0.1:54321/template1"

# Using Docker exec
docker exec micro-supabase psql \
  "postgresql://postgres:password@127.0.0.1:54321/template1?sslmode=disable" \
  -c "SELECT email FROM auth.users;"

Useful Commands

View logs:

docker logs micro-supabase

Follow logs:

docker logs -f micro-supabase

Check running services:

docker exec micro-supabase ps aux

List users:

docker exec micro-supabase psql \
  "postgresql://postgres:password@127.0.0.1:54321/template1?sslmode=disable" \
  -c "SELECT id, email, created_at FROM auth.users;"

Stop and remove:

docker rm -f micro-supabase

Rebuild:

./build.sh
docker rm -f micro-supabase
# In-memory mode
docker run -d --name micro-supabase \
  -p 54321:54321 \
  -p 8080:8080 \
  -v "$(pwd)/migrations:/app/migrations" \
  micro-supabase:latest

# Or with persistent storage
docker run -d --name micro-supabase \
  -p 54321:54321 \
  -p 8080:8080 \
  -e STORAGE_MODE=disk \
  -v "$(pwd)/migrations:/app/migrations" \
  -v "$(pwd)/data:/app/data" \
  micro-supabase:latest

Performance

Startup Time

Micro-Supabase starts up quickly thanks to the embedded pgdata_seed.tar:

Storage Mode Average Startup Time
Memory (default) ~5.9 seconds
Disk (persistent) ~5.8 seconds

Benchmark details:

  • Measured from container start to "Micro-Supabase is running!" message
  • Includes: PostgreSQL initialization, Auth service startup, PostgREST startup, Nginx configuration
  • Tested on Apple Silicon (ARM64)
  • Disk mode is slightly faster due to pre-initialized database state

Image Size

The complete Docker image is 365MB, containing:

  • PostgreSQL runtime (pglited with embedded pgdata): 94.5 MB
  • PostgREST: 71.4 MB
  • GoTrue (Auth): 27.8 MB
  • Debian base + dependencies: 171.6 MB

Architecture

flowchart TB
    subgraph Docker["Docker Container (micro-supabase)"]
        direction TB

        subgraph Services["Internal Services"]
            PG[("pglited<br/>PostgreSQL<br/>:54321")]
            Auth["GoTrue<br/>Auth Service<br/>:9999"]
            REST["PostgREST<br/>REST API<br/>:3000"]
            Proxy["Nginx<br/>Reverse Proxy<br/>:8080"]
        end

        Proxy -->|"/auth/v1/*"| Auth
        Proxy -->|"/rest/v1/*"| REST
        Auth -->|"SQL queries"| PG
        REST -->|"SQL queries"| PG
    end

    Client([Client<br/>Supabase-js]) -->|"HTTP :8080<br/>(public)"| Proxy
    Client -.->|"PostgreSQL :54321<br/>(public)"| PG
Loading

Service Flow:

  1. Client connects to Nginx on port 8080
  2. Nginx routes /auth/v1/* to GoTrue (port 9999)
  3. Nginx routes /rest/v1/* to PostgREST (port 3000)
  4. Both services connect to pglited on port 54321

How It Works

Database Setup

  • Uses PostgreSQL's template1 database
  • Creates two schemas: auth (for GoTrue) and public (for your data)
  • Sets search_path=auth,public so services can find tables
  • Pre-creates auth types needed by GoTrue migrations

Authentication Flow

  1. User signs up via /auth/v1/signup
  2. GoTrue creates user in auth.users table
  3. GoTrue returns JWT token with user ID in claims
  4. Client includes JWT in Authorization: Bearer TOKEN header
  5. PostgREST validates JWT and sets request.jwt.claim.sub (user ID)
  6. RLS policies use auth.uid() to check ownership

Row Level Security

RLS policies automatically filter data based on the authenticated user:

-- Only show user's own records
CREATE POLICY "policy_name" ON table_name
  FOR SELECT
  USING (auth.uid() = user_id);

The auth.uid() function extracts the user ID from the JWT token.

Limitations

  • No realtime subscriptions - Realtime subscriptions are not implemented yet
  • No storage API - File upload/download not implemented
  • Auto-confirm emails - Email verification is skipped for local development
  • Single database - Uses template1, not configurable due to pglited limitations
  • ARM64 only - Docker image is built for ARM64 architecture (Apple Silicon, AWS Graviton). x86_64 support requires rebuilding binaries

Security Notes

⚠️ This is for development only. Do not use in production without changes:

  • JWT secret is hardcoded: super-secret-jwt-token-with-at-least-32-characters-long
  • Database password is password
  • Email auto-confirm is enabled
  • CORS is wide open (*)

For production, change these values and configure proper secrets management.

Troubleshooting

Container won't start:

  • Check if ports 54321 or 8080 are already in use
  • Use docker logs micro-supabase to see errors

Can't connect from host:

  • Make sure you're using 0.0.0.0 not localhost in the URL
  • Check firewall settings

Migrations failing:

  • Ensure migrations are valid SQL
  • Check logs with docker logs micro-supabase
  • Migrations must use auth.uid() for RLS, not direct user_id checks

RLS not working:

  • Verify JWT token is included in Authorization header
  • Check that table has ENABLE ROW LEVEL SECURITY
  • Ensure policies use auth.uid() = user_id

Contributing

This is an experimental project demonstrating how to package Supabase services in a single container. Contributions and improvements are welcome!

License

This project packages several open source components:

  • pglited (MIT)
  • GoTrue (MIT)
  • PostgREST (MIT)
  • Nginx (BSD)

Each component retains its original license.

About

Supabase compatible container

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages