SiamCafe.net Blog
Technology

Supabase Realtime Home Lab Setup

supabase realtime home lab setup
Supabase Realtime Home Lab Setup | SiamCafe Blog
2025-12-21· อ. บอม — SiamCafe.net· 10,681 คำ

Supabase Realtime Lab

Supabase Realtime Home Lab Self-hosted PostgreSQL WebSocket Subscription Broadcast Presence RLS Edge Functions Chat Dashboard Collaboration

FeatureProtocolUse CaseLatencyScale
Postgres ChangesWebSocket + LISTEN/NOTIFYLive data updates50-200ms10K connections
BroadcastWebSocketChat, cursor sharing10-50ms100K messages/s
PresenceWebSocket + CRDTOnline users, typing100-500ms10K users/room
Edge FunctionsDeno DeployServer-side logic5-50msAuto-scale
AuthJWT + GoTrueUser authentication50-200msUnlimited

Self-hosted Setup

# === Supabase Self-hosted with Docker ===

# Clone and setup
# git clone --depth 1 https://github.com/supabase/supabase
# cd supabase/docker
# cp .env.example .env

# Edit .env — Important settings
# POSTGRES_PASSWORD=your-super-secret-password
# JWT_SECRET=your-super-secret-jwt-token-at-least-32-chars
# ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
# SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
# DASHBOARD_USERNAME=admin
# DASHBOARD_PASSWORD=admin123
# SITE_URL=http://localhost:3000

# Start all services
# docker compose up -d

# Services started:
# - PostgreSQL      :5432
# - Supabase Studio :3000 (Dashboard)
# - Kong Gateway    :8000 (API)
# - GoTrue          :9999 (Auth)
# - PostgREST       :3001 (REST API)
# - Realtime        :4000 (WebSocket)
# - Storage         :5000 (File Storage)
# - Meta            :8080 (Metadata)

# Create table with Realtime enabled
# CREATE TABLE messages (
#   id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
#   user_id UUID REFERENCES auth.users NOT NULL,
#   content TEXT NOT NULL,
#   room TEXT NOT NULL DEFAULT 'general',
#   created_at TIMESTAMPTZ DEFAULT NOW()
# );
#
# ALTER TABLE messages ENABLE ROW LEVEL SECURITY;
#
# -- Enable Realtime
# ALTER PUBLICATION supabase_realtime ADD TABLE messages;

from dataclasses import dataclass

@dataclass
class ServiceConfig:
    service: str
    image: str
    port: int
    resource: str
    purpose: str

services = [
    ServiceConfig("PostgreSQL", "supabase/postgres", 5432, "2 CPU 4GB RAM", "Database"),
    ServiceConfig("Realtime", "supabase/realtime", 4000, "1 CPU 1GB RAM", "WebSocket server"),
    ServiceConfig("PostgREST", "postgrest/postgrest", 3001, "0.5 CPU 512MB", "Auto REST API"),
    ServiceConfig("GoTrue", "supabase/gotrue", 9999, "0.5 CPU 512MB", "Authentication"),
    ServiceConfig("Kong", "kong", 8000, "1 CPU 1GB RAM", "API Gateway"),
    ServiceConfig("Studio", "supabase/studio", 3000, "1 CPU 1GB RAM", "Dashboard UI"),
    ServiceConfig("Storage", "supabase/storage-api", 5000, "0.5 CPU 512MB", "File storage"),
]

print("=== Supabase Services ===")
for s in services:
    print(f"  [{s.service}] Port: {s.port} | Image: {s.image}")
    print(f"    Resource: {s.resource} | Purpose: {s.purpose}")

Realtime Client Code

# === Realtime Subscription Examples ===

# JavaScript Client
# import { createClient } from '@supabase/supabase-js'
#
# const supabase = createClient(
#   'http://localhost:8000',
#   'your-anon-key'
# )
#
# // 1. Postgres Changes — Listen to INSERT
# const channel = supabase
#   .channel('messages-channel')
#   .on('postgres_changes',
#     { event: 'INSERT', schema: 'public', table: 'messages',
#       filter: 'room=eq.general' },
#     (payload) => {
#       console.log('New message:', payload.new)
#       addMessageToUI(payload.new)
#     }
#   )
#   .subscribe()
#
# // 2. Broadcast — Send/receive custom events
# const broadcastChannel = supabase
#   .channel('cursor-room')
#   .on('broadcast', { event: 'cursor-move' }, (payload) => {
#     moveCursor(payload.payload.x, payload.payload.y, payload.payload.user)
#   })
#   .subscribe()
#
# // Send cursor position
# broadcastChannel.send({
#   type: 'broadcast',
#   event: 'cursor-move',
#   payload: { x: 100, y: 200, user: 'alice' }
# })
#
# // 3. Presence — Track online users
# const presenceChannel = supabase
#   .channel('online-users')
#   .on('presence', { event: 'sync' }, () => {
#     const state = presenceChannel.presenceState()
#     updateOnlineUsers(Object.keys(state))
#   })
#   .subscribe(async (status) => {
#     if (status === 'SUBSCRIBED') {
#       await presenceChannel.track({
#         user: 'alice', online_at: new Date().toISOString()
#       })
#     }
#   })

# Python Client
# from supabase import create_client
#
# supabase = create_client("http://localhost:8000", "your-anon-key")
#
# def handle_change(payload):
#     print(f"Change: {payload}")
#
# channel = supabase.channel("messages")
# channel.on_postgres_changes(
#     event="INSERT",
#     schema="public",
#     table="messages",
#     callback=handle_change
# ).subscribe()

@dataclass
class RealtimeFeature:
    feature: str
    event_types: str
    use_case: str
    example: str

features = [
    RealtimeFeature("Postgres Changes", "INSERT UPDATE DELETE",
        "Live data sync, dashboard updates",
        "Chat messages, order status, stock prices"),
    RealtimeFeature("Broadcast", "Custom events",
        "Peer-to-peer messaging, no persistence",
        "Cursor sharing, typing indicator, game state"),
    RealtimeFeature("Presence", "sync join leave",
        "Track online users, user state",
        "Online indicators, collaborative editing"),
]

print("\n=== Realtime Features ===")
for f in features:
    print(f"  [{f.feature}] Events: {f.event_types}")
    print(f"    Use case: {f.use_case}")
    print(f"    Example: {f.example}")

RLS and Security

# === Row Level Security Policies ===

# -- Users can read messages in rooms they belong to
# CREATE POLICY "Users can read room messages"
#   ON messages FOR SELECT
#   USING (
#     room IN (
#       SELECT room_id FROM room_members
#       WHERE user_id = auth.uid()
#     )
#   );
#
# -- Users can insert their own messages
# CREATE POLICY "Users can insert own messages"
#   ON messages FOR INSERT
#   WITH CHECK (auth.uid() = user_id);
#
# -- Users can update their own messages
# CREATE POLICY "Users can update own messages"
#   ON messages FOR UPDATE
#   USING (auth.uid() = user_id);
#
# -- Users can delete their own messages
# CREATE POLICY "Users can delete own messages"
#   ON messages FOR DELETE
#   USING (auth.uid() = user_id);
#
# -- Public read for announcements
# CREATE POLICY "Anyone can read announcements"
#   ON announcements FOR SELECT
#   USING (true);

# Edge Function — Server-side validation
# // supabase/functions/validate-message/index.ts
# import { serve } from 'https://deno.land/std/http/server.ts'
# import { createClient } from 'https://esm.sh/@supabase/supabase-js'
#
# serve(async (req) => {
#   const { content, room } = await req.json()
#   if (content.length > 1000) {
#     return new Response('Message too long', { status: 400 })
#   }
#   // Moderate content
#   if (containsBadWords(content)) {
#     return new Response('Inappropriate content', { status: 400 })
#   }
#   const supabase = createClient(
#     Deno.env.get('SUPABASE_URL'),
#     Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')
#   )
#   const { data, error } = await supabase
#     .from('messages')
#     .insert({ content, room, user_id: req.auth.uid })
#   return new Response(JSON.stringify(data))
# })

@dataclass
class SecurityCheck:
    check: str
    status: str
    fix: str

checklist = [
    SecurityCheck("RLS enabled on all tables", "REQUIRED",
        "ALTER TABLE x ENABLE ROW LEVEL SECURITY"),
    SecurityCheck("RLS policies defined", "REQUIRED",
        "CREATE POLICY for SELECT INSERT UPDATE DELETE"),
    SecurityCheck("JWT Secret rotated", "RECOMMENDED",
        "Change JWT_SECRET in .env, restart services"),
    SecurityCheck("Service Role Key protected", "REQUIRED",
        "Never expose in client code, use only server-side"),
    SecurityCheck("Realtime filtered by RLS", "AUTOMATIC",
        "Realtime respects RLS policies automatically"),
    SecurityCheck("Rate limiting configured", "RECOMMENDED",
        "Kong rate-limiting plugin, 100 req/min per user"),
    SecurityCheck("HTTPS enabled", "REQUIRED for production",
        "Use reverse proxy with SSL certificate"),
]

print("Security Checklist:")
for c in checklist:
    print(f"  [{c.status}] {c.check}")
    print(f"    Fix: {c.fix}")

เคล็ดลับ

Supabase Realtime คืออะไร

Real-time Client Database PostgreSQL LISTEN NOTIFY WebSocket Postgres Changes Broadcast Presence Chat Dashboard Collaboration Notification

ตั้ง Self-hosted อย่างไร

Docker Compose PostgreSQL Kong GoTrue PostgREST Realtime Storage Studio .env JWT Secret API Keys Dashboard localhost:3000 Project Table RLS

Realtime Subscription ใช้อย่างไร

Client Library Channel postgres_changes INSERT UPDATE DELETE Broadcast Message Presence Online subscribe Filter Table Column RLS Policy

Row Level Security ตั้งอย่างไร

ENABLE ROW LEVEL SECURITY Policy SELECT INSERT UPDATE DELETE auth.uid() user_id Public Policy ทดสอบ User ต่างกัน

สรุป

Supabase Realtime Home Lab Docker Self-hosted PostgreSQL WebSocket Subscription Broadcast Presence RLS Edge Functions Security Chat Dashboard Production

📖 บทความที่เกี่ยวข้อง

Supabase Realtime Monitoring และ Alertingอ่านบทความ → Supabase Realtime Log Management ELKอ่านบทความ → Supabase Realtime API Gateway Patternอ่านบทความ → Supabase Realtime Learning Path Roadmapอ่านบทความ → Supabase Realtime Tech Conference 2026อ่านบทความ →

📚 ดูบทความทั้งหมด →