Supabaseセルフホスティング - 複数サーバーからの利用
作成日:
Supabase self-hosting PostgreSQL backend infrastructure multi-tenant
概要
Supabaseをセルフホスティングし、1台のサーバーにセットアップしたインスタンスを複数の異なるサーバー(アプリケーション)から利用する構成について解説します。
結論: この構成は有効であり、一般的なユースケースです。
Supabaseは標準的なクライアント-サーバーアーキテクチャを採用しており、REST API・WebSocket経由でどこからでも接続可能です。
この構成のメリット
1. コスト効率
- 1つのSupabaseインスタンスを複数のアプリで共有することで、インフラコストを削減
- クラウド版Supabaseの無料枠制限(プロジェクト数制限など)を回避
2. データの一元管理
- 複数のアプリ間でデータを共有可能
- 分析・レポーティングが容易
- データの整合性を維持しやすい
3. 運用の簡素化
- 1つのデータベースインスタンスの管理で済む
- バックアップ戦略を一元化
- 監視・アラートの設定が簡単
4. ベンダーロックインの回避
- 完全に自分の管理下にある
- いつでも移行可能
- コストの完全な予測が可能
アーキテクチャ
基本構成
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Server A │ │ Server B │ │ Server C │
│ (Webアプリ1) │ │ (Webアプリ2) │ │ (モバイルAPI) │
│ │ │ │ │ │
│ Next.js等 │ │ Laravel等 │ │ Express等 │
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
│ │ │
│ HTTPS (REST API / WebSocket / GraphQL) │
└──────────────────────┼──────────────────────┘
│
┌───────────▼───────────┐
│ Self-hosted Supabase │
│ (VPS / 専用サーバー) │
│ │
│ ┌─────────────────┐ │
│ │ Traefik (TLS) │ │
│ └────────┬────────┘ │
│ │ │
│ ┌────────▼────────┐ │
│ │ Kong (API GW) │ │
│ │ Port: 8000 │ │
│ └────────┬────────┘ │
│ │ │
│ ┌────────┴────────┐ │
│ │ │ │
│ ▼ ▼ │
│ PostgreSQL GoTrue │
│ PostgREST Realtime │
│ Storage Meta │
└───────────────────────┘
Supabaseサービス一覧
| サービス | 役割 | デフォルトポート |
|---|---|---|
| PostgreSQL | データベース | 5432 |
| Kong | API Gateway(外部公開) | 8000 |
| PostgREST | REST API | 3000 |
| GoTrue | 認証サービス | 9999 |
| Realtime | WebSocket通信 | 4000 |
| Storage | ファイル管理 | 5000 |
| Studio | 管理画面 | 3000 |
セットアップ手順
1. Supabaseのセルフホスティング
# Supabase Dockerリポジトリをクローン
git clone --depth 1 https://github.com/supabase/supabase
cd supabase/docker
# 環境変数ファイルをコピー
cp .env.example .env
# 重要: .envファイルを編集
# - POSTGRES_PASSWORD を強力なパスワードに変更
# - JWT_SECRET を変更
# - ANON_KEY, SERVICE_ROLE_KEY を再生成
2. 外部アクセス用の設定
.env ファイルの設定例:
# 外部からアクセスするためのURL
SITE_URL=https://supabase.example.com
API_EXTERNAL_URL=https://api.supabase.example.com
# セキュリティ設定
POSTGRES_PASSWORD=your-super-secure-password
JWT_SECRET=your-super-secret-jwt-token-with-at-least-32-characters
3. Traefikとの統合(推奨)
docker-compose.override.yml の例:
services:
kong:
networks:
- default
- traefik
labels:
- traefik.enable=true
- traefik.http.routers.supabase-api.rule=Host(`api.supabase.example.com`)
- traefik.http.routers.supabase-api.entrypoints=websecure
- traefik.http.routers.supabase-api.tls=true
- traefik.http.routers.supabase-api.tls.certresolver=letsencrypt
- traefik.http.services.supabase-api.loadbalancer.server.port=8000
studio:
networks:
- default
- traefik
labels:
- traefik.enable=true
- traefik.http.routers.supabase-studio.rule=Host(`studio.supabase.example.com`)
- traefik.http.routers.supabase-studio.entrypoints=websecure
- traefik.http.routers.supabase-studio.tls=true
- traefik.http.routers.supabase-studio.tls.certresolver=letsencrypt
- traefik.http.services.supabase-studio.loadbalancer.server.port=3000
networks:
traefik:
external: true
4. 起動
docker-compose up -d
クライアント側の接続設定
JavaScript/TypeScript(各サーバーで共通)
import { createClient } from '@supabase/supabase-js'
// セルフホストSupabaseへの接続
const supabaseUrl = 'https://api.supabase.example.com'
const supabaseKey = 'your-publishable-key'
const supabase = createClient(supabaseUrl, supabaseKey)
// 通常通りデータベース操作が可能
const { data, error } = await supabase
.from('users')
.select('*')
環境変数での管理(推奨)
各サーバーの .env ファイル:
# Server A (.env)
SUPABASE_URL=https://api.supabase.example.com
SUPABASE_ANON_KEY=your-publishable-key
SUPABASE_SERVICE_KEY=your-secret-key # サーバーサイドのみ
// 環境変数から読み込み
const supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_ANON_KEY!
)
マルチテナント設計パターン
複数のアプリケーションが1つのSupabaseインスタンスを共有する場合の設計パターンを紹介します。
パターン1: スキーマ分離(推奨)
各アプリケーションに専用のPostgreSQLスキーマを割り当てる方法です。
-- アプリごとにスキーマを作成
CREATE SCHEMA app_a;
CREATE SCHEMA app_b;
CREATE SCHEMA app_c;
-- app_a用のテーブル
CREATE TABLE app_a.users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email TEXT UNIQUE NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- app_b用のテーブル
CREATE TABLE app_b.products (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
price DECIMAL(10, 2)
);
メリット:
- 完全なデータ分離
- 各アプリの独立したマイグレーション
- セキュリティが明確
パターン2: テナントIDによる分離
同じテーブルを共有し、テナントIDで分離する方法です。
-- 共有テーブルにtenant_idカラムを追加
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id TEXT NOT NULL, -- 'app_a', 'app_b', 'app_c'
email TEXT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(tenant_id, email)
);
-- RLSでテナント分離
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Tenant isolation"
ON users
USING (tenant_id = current_setting('app.tenant_id', true));
メリット:
- シンプルな構成
- データの横断分析が容易
パターン3: 共有データ + 個別データのハイブリッド
-- 共有データ(publicスキーマ)
CREATE TABLE public.master_categories (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL
);
-- 個別データ(各アプリのスキーマ)
CREATE TABLE app_a.orders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
category_id INTEGER REFERENCES public.master_categories(id),
user_id UUID NOT NULL,
total DECIMAL(10, 2)
);
セキュリティ考慮事項
1. ネットワークセキュリティ
| 設定項目 | 推奨設定 |
|---|---|
| TLS/SSL | 必須(Traefikで終端推奨) |
| ファイアウォール | Kong(8000)とStudio(3000)のみ外部公開 |
| PostgreSQL | 外部からの直接接続を禁止(5432は内部のみ) |
| IP制限 | 可能であれば接続元IPを制限 |
2. 認証・認可
-- RLSを必ず有効化
ALTER TABLE your_table ENABLE ROW LEVEL SECURITY;
-- アプリごとに異なるポリシーを設定可能
CREATE POLICY "App A users only"
ON app_a.users
FOR ALL
USING (
auth.jwt() ->> 'app_id' = 'app_a'
);
3. APIキーの管理
| キーの種類 | 用途 | 保管場所 |
|---|---|---|
publishable key | フロントエンド | クライアントコード(公開可) |
secret key | サーバーサイド | 環境変数のみ(絶対に公開しない) |
各サーバーで異なるJWTを使用する場合:
// カスタムJWTを使用してアプリを識別
const supabase = createClient(supabaseUrl, supabaseKey, {
global: {
headers: {
'x-app-id': 'app_a'
}
}
})
パフォーマンス考慮事項
1. レイテンシ対策
| 要因 | 対策 |
|---|---|
| 物理的距離 | Supabaseサーバーを接続元の近くに配置 |
| 接続数 | コネクションプーリングを使用 |
| クエリ最適化 | 適切なインデックスを設定 |
2. リソース配分
# docker-compose.override.yml でリソース制限
services:
db:
deploy:
resources:
limits:
cpus: '2'
memory: 4G
reservations:
cpus: '1'
memory: 2G
3. 監視
# PostgreSQLの接続数確認
SELECT count(*) FROM pg_stat_activity;
# アクティブなクエリの確認
SELECT pid, query, state, wait_event_type
FROM pg_stat_activity
WHERE state = 'active';
注意点・制限事項
1. スケーラビリティの限界
- 複数アプリの負荷が1つのインスタンスに集中
- 高負荷時は水平スケーリングが困難(PostgreSQLのレプリケーション設定が必要)
2. 障害の影響範囲
- Supabaseがダウンすると全アプリに影響
- 高可用性が必要な場合はレプリケーション構成を検討
3. アップグレード時の調整
- Supabaseのアップグレード時に全アプリのテストが必要
- マイグレーションの調整が必要な場合がある
代替構成
複数Supabaseインスタンスを使用する場合
完全な分離が必要な場合は、アプリごとに別のSupabaseインスタンスを立てることも検討できます。
App A → Supabase Instance A (supabase-a.example.com)
App B → Supabase Instance B (supabase-b.example.com)
App C → Supabase Instance C (supabase-c.example.com)
この構成が適している場合:
- 各アプリの負荷が大きく異なる
- 完全な障害分離が必要
- 各アプリのチームが独立して運用
まとめ
| 観点 | 評価 |
|---|---|
| 技術的な実現可能性 | ✅ 問題なし |
| コスト効率 | ✅ 高い |
| 運用の複雑さ | ⚠️ 中程度(マルチテナント設計が必要) |
| スケーラビリティ | ⚠️ 計画的な設計が必要 |
| セキュリティ | ✅ RLSで十分に対応可能 |
推奨されるユースケース:
- 個人・小規模チームでの複数アプリ開発
- 関連性のあるアプリ群でのデータ共有
- コストを抑えたい場合
非推奨のユースケース:
- 各アプリに完全な独立性が必要な場合
- 極めて高い可用性が求められる場合
- 各アプリの負荷が予測できない場合