付録: 実践的リファレンス集 📚


📚 目次に戻る: 📖 学習ガイド
🔗 関連章: 全章 で参照されるリファレンス資料集
🎯 用途: 開発中の即座な参照・トラブルシューティング・実装支援
⏱️ 利用方法: 必要時に該当セクションを参照
📝 レベル: 全レベル対応(基礎〜上級) —

📖 付録の使い方

この付録は各章での学習・実装を支援する実践的リファレンス集です:

  • 🔧 環境構築: 開発環境の自動セットアップスクリプト
  • 📋 チェックリスト: 実装・運用時の確認項目
  • 🚨 トラブルシューティング: よくある問題と解決方法
  • 💡 ベストプラクティス: 実用的な設計・実装パターン
  • 📊 設定サンプル: そのまま使える設定ファイル集

📍 各章からの参照

主な参照セクション 用途
Chapter 1-2 A. 環境構築、G. トラブルシューティング 基礎環境準備・初期設定
Chapter 3-5 B. 設定テンプレート、C. コードスニペット 実装サポート・設定例
Chapter 6-8 D. パフォーマンス、E. セキュリティ 最適化・運用設定
Chapter 9-10 F. デプロイメント、H. ベストプラクティス 実践・統合支援

A. 環境構築スクリプト集

A.1 Supabase開発環境セットアップ

#!/bin/bash
# setup_supabase_dev.sh
# Supabase開発環境の完全自動セットアップ

set -euo pipefail

# 設定変数
PROJECT_NAME="${1:-supabase-app}"
POSTGRES_PASSWORD="${2:-$(openssl rand -base64 32)}"
JWT_SECRET="${3:-$(openssl rand -base64 64)}"

echo "🚀 Supabase開発環境セットアップ開始"
echo "プロジェクト名: $PROJECT_NAME"

# 必要なツールのチェック
check_dependencies() {
    local deps=("docker" "docker-compose" "psql" "curl" "jq")
    for dep in "${deps[@]}"; do
        if ! command -v "$dep" &> /dev/null; then
            echo "❌ $dep がインストールされていません"
            exit 1
        fi
    done
    echo "✅ 依存関係チェック完了"
}

# プロジェクトディレクトリ作成
setup_project_structure() {
    mkdir -p "$PROJECT_NAME"/{supabase/{migrations,seed,functions,volumes/{db/{data,init},api}},apps/{flet-client,fastapi-server},scripts,docs}
    cd "$PROJECT_NAME"
    echo "✅ プロジェクト構造作成完了"
}

# 環境変数ファイル生成
create_env_files() {
    cat > .env << EOF
# Supabase設定
SUPABASE_URL=http://localhost:54321
SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0
SUPABASE_SERVICE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU

# データベース設定
POSTGRES_HOST=localhost
POSTGRES_PORT=54322
POSTGRES_DB=postgres
POSTGRES_USER=postgres
POSTGRES_PASSWORD=$POSTGRES_PASSWORD

# JWT設定
JWT_SECRET=$JWT_SECRET

# API設定
API_PORT=8000
API_HOST=0.0.0.0

# 外部サービス(本番環境で設定)
STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=
SENDGRID_API_KEY=
SLACK_WEBHOOK_URL=
EOF

    # 本番用環境変数テンプレート
    cat > .env.production.template << EOF
# 本番環境用設定テンプレート
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_KEY=your-service-key

POSTGRES_HOST=your-production-host
POSTGRES_PORT=5432
POSTGRES_DB=postgres
POSTGRES_USER=postgres
POSTGRES_PASSWORD=your-secure-password

JWT_SECRET=your-production-jwt-secret

# 外部サービス
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
SENDGRID_API_KEY=SG....
SLACK_WEBHOOK_URL=https://hooks.slack.com/...
EOF

    echo "✅ 環境変数ファイル作成完了"
}

# Docker Compose設定生成
create_docker_compose() {
    cat > docker-compose.yml << 'EOF'
version: '3.8'

services:
  # PostgreSQL データベース
  supabase-db:
    image: supabase/postgres:15.1.0.117
    container_name: supabase-db
    environment:
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: postgres
    volumes:
      - ./supabase/volumes/db/data:/var/lib/postgresql/data
      - ./supabase/volumes/db/init:/docker-entrypoint-initdb.d
    ports:
      - "54322:5432"
    command: postgres -c wal_level=logical
    restart: unless-stopped

  # Supabase Studio(管理画面)
  supabase-studio:
    image: supabase/studio:20231123-fb9c70e
    container_name: supabase-studio
    environment:
      SUPABASE_URL: http://localhost:54321
      SUPABASE_ANON_KEY: ${SUPABASE_ANON_KEY}
      SUPABASE_SERVICE_KEY: ${SUPABASE_SERVICE_KEY}
    ports:
      - "3000:3000"
    depends_on:
      - supabase-kong
    restart: unless-stopped

  # Kong API Gateway
  supabase-kong:
    image: kong:2.8.1
    container_name: supabase-kong
    environment:
      KONG_DATABASE: "off"
      KONG_DECLARATIVE_CONFIG: /var/lib/kong/kong.yml
      KONG_DNS_ORDER: LAST,A,CNAME
      KONG_PLUGINS: request-transformer,cors,key-auth,acl
    volumes:
      - ./supabase/volumes/api/kong.yml:/var/lib/kong/kong.yml:ro
    ports:
      - "54321:8000"
      - "54323:8001"
    depends_on:
      - supabase-auth
      - supabase-rest
      - supabase-realtime
    restart: unless-stopped

  # Supabase Auth(認証サーバー)
  supabase-auth:
    image: supabase/gotrue:v2.99.0
    container_name: supabase-auth
    environment:
      GOTRUE_API_HOST: 0.0.0.0
      GOTRUE_API_PORT: 9999
      GOTRUE_DB_DRIVER: postgres
      GOTRUE_DB_DATABASE_URL: postgres://postgres:${POSTGRES_PASSWORD}@supabase-db:5432/postgres?search_path=auth
      GOTRUE_SITE_URL: http://localhost:3000
      GOTRUE_URI_ALLOW_LIST: http://localhost:3000,http://localhost:8000
      GOTRUE_JWT_SECRET: ${JWT_SECRET}
      GOTRUE_JWT_EXP: 3600
      GOTRUE_JWT_DEFAULT_GROUP_NAME: authenticated
      GOTRUE_JWT_ADMIN_GROUP_NAME: service_role
      GOTRUE_EXTERNAL_EMAIL_ENABLED: true
      GOTRUE_MAILER_AUTOCONFIRM: true
      GOTRUE_SMTP_HOST: 
      GOTRUE_SMTP_PORT: 587
      GOTRUE_SMTP_USER: 
      GOTRUE_SMTP_PASS: 
    depends_on:
      - supabase-db
    restart: unless-stopped

  # PostgREST(自動API生成)
  supabase-rest:
    image: postgrest/postgrest:v11.2.0
    container_name: supabase-rest
    environment:
      PGRST_DB_URI: postgres://postgres:${POSTGRES_PASSWORD}@supabase-db:5432/postgres
      PGRST_DB_SCHEMAS: public,storage,graphql_public
      PGRST_DB_ANON_ROLE: anon
      PGRST_JWT_SECRET: ${JWT_SECRET}
      PGRST_DB_USE_LEGACY_GUCS: "false"
    depends_on:
      - supabase-db
    restart: unless-stopped

  # Realtime(リアルタイム機能)
  supabase-realtime:
    image: supabase/realtime:v2.25.35
    container_name: supabase-realtime
    environment:
      PORT: 4000
      DB_HOST: supabase-db
      DB_PORT: 5432
      DB_USER: postgres
      DB_PASSWORD: ${POSTGRES_PASSWORD}
      DB_NAME: postgres
      DB_AFTER_CONNECT_QUERY: 'SET search_path TO _realtime'
      DB_ENC_KEY: supabaserealtime
      API_JWT_SECRET: ${JWT_SECRET}
      SECRET_KEY_BASE: UpNVntn3cDxHJpq99YMc1T1AQgQpc8kfYTuRgBiYa15BLrx8etQoXz3gZv1/u2oq
      ERL_AFLAGS: -proto_dist inet_tcp
      ENABLE_TAILSCALE: "false"
      DNS_NODES: "''"
    depends_on:
      - supabase-db
    command: >
      bash -c "
        /app/bin/migrate && 
        /app/bin/realtime eval 'Realtime.Release.seeds(Realtime.Repo)' && 
        /app/bin/server
      "
    restart: unless-stopped

  # Redis(キャッシュ・セッション)
  redis:
    image: redis:7-alpine
    container_name: supabase-redis
    ports:
      - "6379:6379"
    command: redis-server --appendonly yes
    volumes:
      - redis_data:/data
    restart: unless-stopped

volumes:
  redis_data:
EOF

    echo "✅ Docker Compose設定作成完了"
}

# Kong設定生成
create_kong_config() {
    mkdir -p supabase/volumes/api
    cat > supabase/volumes/api/kong.yml << 'EOF'
_format_version: "2.1"
_transform: true

consumers:
  - username: anon
    keyauth_credentials:
      - key: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0
  - username: service_role
    keyauth_credentials:
      - key: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU

acls:
  - consumer: anon
    group: anon
  - consumer: service_role
    group: service_role

services:
  - name: auth-v1-open
    url: http://supabase-auth:9999/
    routes:
      - name: auth-v1-open
        strip_path: true
        paths:
          - "/auth/v1/signup"
          - "/auth/v1/token"
          - "/auth/v1/verify"
          - "/auth/v1/recover"
          - "/auth/v1/settings"

  - name: auth-v1-open-verify
    url: http://supabase-auth:9999/verify
    routes:
      - name: auth-v1-open-verify
        strip_path: true
        paths:
          - "/auth/v1/verify"

  - name: auth-v1
    _comment: "GoTrue: /auth/v1/* -> http://supabase-auth:9999/*"
    url: http://supabase-auth:9999/
    routes:
      - name: auth-v1-all
        strip_path: true
        paths:
          - "/auth/v1/"
    plugins:
      - name: cors

  - name: rest-v1
    _comment: "PostgREST: /rest/v1/* -> http://supabase-rest:3000/*"
    url: http://supabase-rest:3000/
    routes:
      - name: rest-v1-all
        strip_path: true
        paths:
          - "/rest/v1/"
    plugins:
      - name: cors
      - name: key-auth
        config:
          hide_credentials: false

  - name: realtime-v1
    _comment: "Realtime: /realtime/v1/* -> ws://supabase-realtime:4000/socket/*"
    url: http://supabase-realtime:4000/socket/
    routes:
      - name: realtime-v1-all
        strip_path: true
        paths:
          - "/realtime/v1/"
    plugins:
      - name: cors
      - name: key-auth
        config:
          hide_credentials: false
EOF

    echo "✅ Kong設定作成完了"
}

# 初期マイグレーション作成
create_initial_migration() {
    cat > supabase/migrations/001_initial_schema.sql << 'EOF'
-- 初期スキーマ設定
-- 拡張機能有効化
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
CREATE EXTENSION IF NOT EXISTS "pg_stat_statements";

-- カスタム型定義
CREATE TYPE user_role AS ENUM ('admin', 'member', 'viewer');
CREATE TYPE task_priority AS ENUM ('low', 'medium', 'high', 'urgent');
CREATE TYPE task_status AS ENUM ('todo', 'in_progress', 'completed', 'cancelled');

-- プロファイルテーブル
CREATE TABLE profiles (
    id UUID REFERENCES auth.users(id) PRIMARY KEY,
    display_name TEXT,
    avatar_url TEXT,
    role user_role DEFAULT 'member',
    metadata JSONB DEFAULT '{}',
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

-- 組織テーブル
CREATE TABLE organizations (
    id BIGSERIAL PRIMARY KEY,
    name TEXT NOT NULL,
    slug TEXT UNIQUE NOT NULL,
    description TEXT,
    settings JSONB DEFAULT '{}',
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

-- 組織メンバーシップ
CREATE TABLE organization_members (
    id BIGSERIAL PRIMARY KEY,
    organization_id BIGINT REFERENCES organizations(id) ON DELETE CASCADE,
    user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
    role user_role DEFAULT 'member',
    joined_at TIMESTAMPTZ DEFAULT NOW(),
    UNIQUE(organization_id, user_id)
);

-- プロジェクトテーブル
CREATE TABLE projects (
    id BIGSERIAL PRIMARY KEY,
    organization_id BIGINT REFERENCES organizations(id) ON DELETE CASCADE,
    name TEXT NOT NULL,
    description TEXT,
    status TEXT DEFAULT 'active',
    created_by UUID REFERENCES auth.users(id),
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

-- タスクテーブル
CREATE TABLE tasks (
    id BIGSERIAL PRIMARY KEY,
    project_id BIGINT REFERENCES projects(id) ON DELETE CASCADE,
    title TEXT NOT NULL,
    description TEXT,
    priority task_priority DEFAULT 'medium',
    status task_status DEFAULT 'todo',
    assignee_id UUID REFERENCES auth.users(id),
    due_date DATE,
    completed_at TIMESTAMPTZ,
    created_by UUID REFERENCES auth.users(id),
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

-- インデックス作成
CREATE INDEX idx_profiles_role ON profiles(role);
CREATE INDEX idx_organization_members_org_id ON organization_members(organization_id);
CREATE INDEX idx_organization_members_user_id ON organization_members(user_id);
CREATE INDEX idx_projects_organization_id ON projects(organization_id);
CREATE INDEX idx_projects_created_by ON projects(created_by);
CREATE INDEX idx_tasks_project_id ON tasks(project_id);
CREATE INDEX idx_tasks_assignee_id ON tasks(assignee_id);
CREATE INDEX idx_tasks_status ON tasks(status);
CREATE INDEX idx_tasks_due_date ON tasks(due_date) WHERE due_date IS NOT NULL;

-- RLS有効化
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
ALTER TABLE organizations ENABLE ROW LEVEL SECURITY;
ALTER TABLE organization_members ENABLE ROW LEVEL SECURITY;
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
ALTER TABLE tasks ENABLE ROW LEVEL SECURITY;

-- プロファイルのRLSポリシー
CREATE POLICY "Users can view own profile" ON profiles
    FOR SELECT USING (auth.uid() = id);

CREATE POLICY "Users can update own profile" ON profiles
    FOR UPDATE USING (auth.uid() = id);

-- 組織のRLSポリシー
CREATE POLICY "Organization members can view" ON organizations
    FOR SELECT USING (
        EXISTS (
            SELECT 1 FROM organization_members 
            WHERE organization_id = organizations.id 
            AND user_id = auth.uid()
        )
    );

-- タスクのRLSポリシー
CREATE POLICY "Task visibility" ON tasks
    FOR SELECT USING (
        EXISTS (
            SELECT 1 FROM projects p
            JOIN organization_members om ON p.organization_id = om.organization_id
            WHERE p.id = tasks.project_id 
            AND om.user_id = auth.uid()
        )
    );

-- 更新日時の自動更新関数
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = NOW();
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- 更新日時トリガー
CREATE TRIGGER update_profiles_updated_at 
    BEFORE UPDATE ON profiles 
    FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

CREATE TRIGGER update_organizations_updated_at 
    BEFORE UPDATE ON organizations 
    FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

CREATE TRIGGER update_projects_updated_at 
    BEFORE UPDATE ON projects 
    FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

CREATE TRIGGER update_tasks_updated_at 
    BEFORE UPDATE ON tasks 
    FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

-- プロファイル自動作成関数
CREATE OR REPLACE FUNCTION handle_new_user()
RETURNS TRIGGER AS $$
BEGIN
    INSERT INTO profiles (id, display_name)
    VALUES (NEW.id, NEW.email);
    RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

-- ユーザー作成時のトリガー
CREATE TRIGGER on_auth_user_created
    AFTER INSERT ON auth.users
    FOR EACH ROW EXECUTE FUNCTION handle_new_user();
EOF

    echo "✅ 初期マイグレーション作成完了"
}

# 開発用スクリプト生成
create_dev_scripts() {
    mkdir -p scripts

    # 起動スクリプト
    cat > scripts/start.sh << 'EOF'
#!/bin/bash
echo "🚀 Supabase開発環境起動中..."
docker-compose up -d

echo "⏳ サービス起動待機中..."
sleep 10

# ヘルスチェック
echo "🏥 ヘルスチェック実行中..."
curl -f http://localhost:54321/rest/v1/ > /dev/null 2>&1 && echo "✅ PostgREST: OK" || echo "❌ PostgREST: NG"
curl -f http://localhost:54321/auth/v1/settings > /dev/null 2>&1 && echo "✅ Auth: OK" || echo "❌ Auth: NG"
curl -f http://localhost:3000 > /dev/null 2>&1 && echo "✅ Studio: OK" || echo "❌ Studio: NG"

echo "🌟 Supabase開発環境が利用可能です"
echo "📊 Studio: http://localhost:3000"
echo "🔗 API: http://localhost:54321"
echo "💾 Database: localhost:54322"
EOF

    # 停止スクリプト
    cat > scripts/stop.sh << 'EOF'
#!/bin/bash
echo "🛑 Supabase開発環境停止中..."
docker-compose down
echo "✅ 停止完了"
EOF

    # リセットスクリプト
    cat > scripts/reset.sh << 'EOF'
#!/bin/bash
echo "🔄 Supabase開発環境リセット中..."
docker-compose down -v
docker-compose up -d
echo "✅ リセット完了"
EOF

    # バックアップスクリプト
    cat > scripts/backup.sh << 'EOF'
#!/bin/bash
BACKUP_DIR="backups/$(date +%Y%m%d_%H%M%S)"
mkdir -p "$BACKUP_DIR"

echo "💾 データベースバックアップ中..."
docker-compose exec -T supabase-db pg_dump -U postgres -d postgres > "$BACKUP_DIR/database.sql"

echo "📁 設定ファイルバックアップ中..."
cp -r supabase/migrations "$BACKUP_DIR/"
cp docker-compose.yml "$BACKUP_DIR/"
cp .env "$BACKUP_DIR/env.backup"

echo "✅ バックアップ完了: $BACKUP_DIR"
EOF

    chmod +x scripts/*.sh
    echo "✅ 開発スクリプト作成完了"
}

# メイン実行
main() {
    check_dependencies
    setup_project_structure
    create_env_files
    create_docker_compose
    create_kong_config
    create_initial_migration
    create_dev_scripts

    echo ""
    echo "🎉 Supabase開発環境セットアップ完了!"
    echo ""
    echo "次のステップ:"
    echo "1. cd $PROJECT_NAME"
    echo "2. ./scripts/start.sh"
    echo "3. http://localhost:3000 でStudioにアクセス"
    echo ""
    echo "パスワード: $POSTGRES_PASSWORD"
    echo "JWT Secret: $JWT_SECRET"
}

main "$@"

A.2 本番環境デプロイスクリプト

#!/bin/bash
# deploy_production.sh
# 本番環境への安全なデプロイ

set -euo pipefail

ENVIRONMENT="${1:-staging}"
VERSION="${2:-latest}"

echo "🚀 $ENVIRONMENT 環境へのデプロイ開始 (version: $VERSION)"

# 前提条件チェック
check_prerequisites() {
    local required_tools=("kubectl" "helm" "docker" "jq")
    for tool in "${required_tools[@]}"; do
        if ! command -v "$tool" &> /dev/null; then
            echo "❌ $tool が必要です"
            exit 1
        fi
    done

    # 環境変数チェック
    if [[ -z "${KUBECONFIG:-}" ]]; then
        echo "❌ KUBECONFIG が設定されていません"
        exit 1
    fi

    echo "✅ 前提条件チェック完了"
}

# イメージビルドとプッシュ
build_and_push_images() {
    echo "🔨 Docker イメージビルド中..."
    
    # FastAPI アプリケーション
    docker build -t "myapp/api:$VERSION" ./apps/fastapi-server/
    docker push "myapp/api:$VERSION"
    
    # Flet クライアント(必要に応じて)
    if [[ -f "./apps/flet-client/Dockerfile" ]]; then
        docker build -t "myapp/client:$VERSION" ./apps/flet-client/
        docker push "myapp/client:$VERSION"
    fi
    
    echo "✅ イメージプッシュ完了"
}

# データベースマイグレーション
run_migrations() {
    echo "📊 データベースマイグレーション実行中..."
    
    # Kubernetesジョブでマイグレーション実行
    kubectl apply -f - <<EOF
apiVersion: batch/v1
kind: Job
metadata:
  name: migration-$VERSION
  namespace: $ENVIRONMENT
spec:
  template:
    spec:
      restartPolicy: Never
      containers:
      - name: migration
        image: myapp/api:$VERSION
        command: ["python", "-m", "alembic", "upgrade", "head"]
        env:
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: app-secrets
              key: database_url
EOF

    # ジョブ完了待機
    kubectl wait --for=condition=complete job/migration-$VERSION -n $ENVIRONMENT --timeout=300s
    
    echo "✅ マイグレーション完了"
}

# アプリケーションデプロイ
deploy_application() {
    echo "🚢 アプリケーションデプロイ中..."
    
    # Helm チャートでデプロイ
    helm upgrade --install myapp ./helm/myapp \
        --namespace $ENVIRONMENT \
        --set image.tag=$VERSION \
        --set environment=$ENVIRONMENT \
        --values ./helm/values-$ENVIRONMENT.yaml \
        --wait
    
    echo "✅ アプリケーションデプロイ完了"
}

# ヘルスチェック
health_check() {
    echo "🏥 ヘルスチェック実行中..."
    
    local api_url
    if [[ "$ENVIRONMENT" == "production" ]]; then
        api_url="https://api.myapp.com"
    else
        api_url="https://api-staging.myapp.com"
    fi
    
    local max_attempts=30
    local attempt=1
    
    while [[ $attempt -le $max_attempts ]]; do
        if curl -f "$api_url/health" > /dev/null 2>&1; then
            echo "✅ ヘルスチェック成功"
            return 0
        fi
        
        echo "⏳ ヘルスチェック失敗 ($attempt/$max_attempts)"
        sleep 10
        ((attempt++))
    done
    
    echo "❌ ヘルスチェック失敗"
    return 1
}

# ロールバック関数
rollback() {
    echo "🔄 ロールバック実行中..."
    
    local previous_version
    previous_version=$(helm history myapp -n $ENVIRONMENT --max 2 -o json | jq -r '.[1].app_version')
    
    helm rollback myapp -n $ENVIRONMENT
    
    echo "✅ ロールバック完了 (version: $previous_version)"
}

# メイン実行
main() {
    check_prerequisites
    
    # バックアップ作成
    echo "💾 デプロイ前バックアップ作成中..."
    ./scripts/backup_production.sh
    
    build_and_push_images
    run_migrations
    deploy_application
    
    if ! health_check; then
        echo "❌ デプロイ失敗 - ロールバック実行"
        rollback
        exit 1
    fi
    
    echo "🎉 デプロイ成功!"
    echo "🌐 URL: https://$(kubectl get ingress myapp-ingress -n $ENVIRONMENT -o jsonpath='{.spec.rules[0].host}')"
}

main "$@"

B. 設定ファイルテンプレート

B.1 Kubernetes設定

# k8s/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: production
  labels:
    environment: production
---
apiVersion: v1
kind: Namespace
metadata:
  name: staging
  labels:
    environment: staging
# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: fastapi-app
  namespace: \{\{ .Values.namespace \}\}
spec:
  replicas: \{\{ .Values.replicas \}\}
  selector:
    matchLabels:
      app: fastapi-app
  template:
    metadata:
      labels:
        app: fastapi-app
    spec:
      containers:
      - name: api
        image: \{\{ .Values.image.repository \}\}:\{\{ .Values.image.tag \}\}
        ports:
        - containerPort: 8000
        env:
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: app-secrets
              key: database_url
        - name: SUPABASE_URL
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: supabase_url
        - name: JWT_SECRET
          valueFrom:
            secretKeyRef:
              name: app-secrets
              key: jwt_secret
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /health
            port: 8000
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /health
            port: 8000
          initialDelaySeconds: 5
          periodSeconds: 5

B.2 GitHub Actions CI/CD

# .github/workflows/ci-cd.yml
name: CI/CD Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: $

jobs:
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: test_db
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432

    steps:
    - uses: actions/checkout@v4
    
    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: '3.11'
    
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements.txt
        pip install -r requirements-test.txt
    
    - name: Run tests
      env:
        DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_db
        JWT_SECRET: test-secret
      run: |
        pytest tests/ -v --cov=app --cov-report=xml
    
    - name: Upload coverage
      uses: codecov/codecov-action@v3
      with:
        file: ./coverage.xml

  security-scan:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    
    - name: Run Trivy vulnerability scanner
      uses: aquasecurity/trivy-action@master
      with:
        scan-type: 'fs'
        scan-ref: '.'
        format: 'sarif'
        output: 'trivy-results.sarif'
    
    - name: Upload Trivy scan results
      uses: github/codeql-action/upload-sarif@v2
      with:
        sarif_file: 'trivy-results.sarif'

  build-and-push:
    needs: [test, security-scan]
    runs-on: ubuntu-latest
    outputs:
      image-digest: $
    steps:
    - uses: actions/checkout@v4
    
    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@v3
    
    - name: Log in to Container Registry
      uses: docker/login-action@v3
      with:
        registry: $
        username: $
        password: $
    
    - name: Extract metadata
      id: meta
      uses: docker/metadata-action@v5
      with:
        images: $/$
        tags: |
          type=ref,event=branch
          type=ref,event=pr
          type=sha,prefix=-
          type=raw,value=latest,enable=
    
    - name: Build and push
      id: build
      uses: docker/build-push-action@v5
      with:
        context: .
        platforms: linux/amd64,linux/arm64
        push: true
        tags: $
        labels: $
        cache-from: type=gha
        cache-to: type=gha,mode=max

  deploy-staging:
    needs: build-and-push
    if: github.ref == 'refs/heads/develop'
    runs-on: ubuntu-latest
    environment: staging
    steps:
    - uses: actions/checkout@v4
    
    - name: Deploy to staging
      run: |
        echo "Deploying to staging environment"
        # Kubernetes/Helm deployment commands

  deploy-production:
    needs: build-and-push
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    environment: production
    steps:
    - uses: actions/checkout@v4
    
    - name: Deploy to production
      run: |
        echo "Deploying to production environment"
        # Kubernetes/Helm deployment commands

B.3 FastAPI設定テンプレート

# app/core/config.py
from pydantic_settings import BaseSettings
from typing import Optional, List
import secrets

class Settings(BaseSettings):
    # API設定
    API_V1_STR: str = "/api/v1"
    SECRET_KEY: str = secrets.token_urlsafe(32)
    ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 8  # 8日
    
    # CORS設定
    BACKEND_CORS_ORIGINS: List[str] = ["http://localhost:3000"]
    
    # データベース設定
    POSTGRES_SERVER: str
    POSTGRES_USER: str
    POSTGRES_PASSWORD: str
    POSTGRES_DB: str
    POSTGRES_PORT: int = 5432
    
    # Supabase設定
    SUPABASE_URL: str
    SUPABASE_ANON_KEY: str
    SUPABASE_SERVICE_KEY: str
    
    # Redis設定
    REDIS_URL: str = "redis://localhost:6379"
    
    # 外部サービス
    STRIPE_SECRET_KEY: Optional[str] = None
    SENDGRID_API_KEY: Optional[str] = None
    
    # 監視設定
    SENTRY_DSN: Optional[str] = None
    LOG_LEVEL: str = "INFO"
    
    # 本番環境固有設定
    ENVIRONMENT: str = "development"
    DEBUG: bool = True
    
    @property
    def DATABASE_URL(self) -> str:
        return f"postgresql://{self.POSTGRES_USER}:{self.POSTGRES_PASSWORD}@{self.POSTGRES_SERVER}:{self.POSTGRES_PORT}/{self.POSTGRES_DB}"
    
    @property
    def ASYNC_DATABASE_URL(self) -> str:
        return f"postgresql+asyncpg://{self.POSTGRES_USER}:{self.POSTGRES_PASSWORD}@{self.POSTGRES_SERVER}:{self.POSTGRES_PORT}/{self.POSTGRES_DB}"

    class Config:
        env_file = ".env"
        case_sensitive = True

settings = Settings()

C. チェックリスト集

C.1 セキュリティチェックリスト

開発環境

  • 環境変数にシークレット情報が含まれていないか
  • デフォルトパスワードが変更されているか
  • 不要なポートが開放されていないか
  • RLSポリシーが適切に設定されているか
  • JWTトークンの有効期限が適切か
  • 入力値のバリデーションが実装されているか
  • SQLインジェクション対策が実装されているか
  • XSS対策が実装されているか
  • CSRF対策が実装されているか

本番環境

  • HTTPS通信が強制されているか
  • セキュリティヘッダーが設定されているか
  • IPホワイトリストが設定されているか
  • ファイアウォール設定が適切か
  • データベースの暗号化が有効か
  • バックアップの暗号化が有効か
  • 監査ログが有効になっているか
  • 脆弱性スキャンが定期実行されているか
  • セキュリティアップデートが適用されているか
  • アクセス権限の最小化が実装されているか

C.2 パフォーマンスチェックリスト

データベース

  • 適切なインデックスが作成されているか
  • スロークエリが最適化されているか
  • 接続プールが適切に設定されているか
  • VACUUM・ANALYZEが定期実行されているか
  • パーティショニングが検討されているか
  • 統計情報が最新か
  • デッドロックが発生していないか

アプリケーション

  • APIレスポンス時間が要件を満たしているか
  • キャッシュ戦略が実装されているか
  • 不要なN+1クエリが除去されているか
  • 非同期処理が適切に実装されているか
  • メモリリークがないか
  • エラー処理が適切に実装されているか

インフラストラクチャ

  • 適切なリソース制限が設定されているか
  • オートスケーリングが設定されているか
  • ロードバランサーが適切に設定されているか
  • CDNが設定されているか
  • 監視・アラートが設定されているか

C.3 運用チェックリスト

デプロイ前

  • テストカバレッジが十分か
  • セキュリティスキャンが完了しているか
  • パフォーマンステストが完了しているか
  • ドキュメントが最新か
  • ロールバック手順が準備されているか
  • 関係者への通知が完了しているか

デプロイ後

  • ヘルスチェックが成功しているか
  • エラー率が正常範囲内か
  • パフォーマンスメトリクスが正常か
  • ログに異常がないか
  • ユーザーからの問い合わせがないか
  • 外部サービス連携が正常か

定期メンテナンス

  • バックアップが正常に取得されているか
  • ログローテーションが実行されているか
  • セキュリティパッチが適用されているか
  • リソース使用量の傾向分析が実施されているか
  • 容量計画の見直しが実施されているか
  • インシデント対応手順の見直しが実施されているか

D. リファレンス

D.1 Supabase API リファレンス

PostgREST クエリパターン

// 基本クエリ
const { data, error } = await supabase
  .from('tasks')
  .select('*')

// 選択的フィールド
const { data, error } = await supabase
  .from('tasks')
  .select('id, title, status')

// 関連データ含む
const { data, error } = await supabase
  .from('tasks')
  .select(`
    id, 
    title, 
    assignee:assignee_id(id, name),
    project:project_id(id, name)
  `)

// フィルタリング
const { data, error } = await supabase
  .from('tasks')
  .select('*')
  .eq('status', 'completed')
  .gte('created_at', '2024-01-01')
  .order('created_at', { ascending: false })
  .limit(10)

// 複雑なフィルタ
const { data, error } = await supabase
  .from('tasks')
  .select('*')
  .or('status.eq.urgent,priority.eq.high')
  .in('assignee_id', ['uuid1', 'uuid2'])

// 全文検索
const { data, error } = await supabase
  .from('tasks')
  .select('*')
  .textSearch('title', 'important task')

// 集計
const { count, error } = await supabase
  .from('tasks')
  .select('*', { count: 'exact', head: true })
  .eq('status', 'completed')

リアルタイム機能

// テーブル変更監視
const subscription = supabase
  .channel('tasks_channel')
  .on('postgres_changes', {
    event: '*',
    schema: 'public',
    table: 'tasks'
  }, (payload) => {
    console.log('Change received!', payload)
  })
  .subscribe()

// 特定条件の監視
const subscription = supabase
  .channel('my_tasks_channel')
  .on('postgres_changes', {
    event: 'UPDATE',
    schema: 'public',
    table: 'tasks',
    filter: 'assignee_id=eq.user-uuid'
  }, (payload) => {
    console.log('My task updated!', payload)
  })
  .subscribe()

// プレゼンス機能
const channel = supabase.channel('online_users')

channel
  .on('presence', { event: 'sync' }, () => {
    const newState = channel.presenceState()
    console.log('sync', newState)
  })
  .on('presence', { event: 'join' }, ({ key, newPresences }) => {
    console.log('join', key, newPresences)
  })
  .on('presence', { event: 'leave' }, ({ key, leftPresences }) => {
    console.log('leave', key, leftPresences)
  })
  .subscribe(async (status) => {
    if (status === 'SUBSCRIBED') {
      await channel.track({
        user_id: 'user-uuid',
        online_at: new Date().toISOString(),
      })
    }
  })

D.2 PostgreSQL / RLS パターン集

高度なRLSポリシー

-- 動的条件ポリシー
CREATE POLICY "Dynamic project access" ON tasks
    FOR ALL USING (
        CASE 
            WHEN current_setting('app.user_role', true) = 'admin' THEN true
            WHEN current_setting('app.user_role', true) = 'manager' THEN 
                project_id IN (
                    SELECT id FROM projects 
                    WHERE manager_id = auth.uid()
                )
            ELSE 
                assignee_id = auth.uid()
        END
    );

-- 時間制限ポリシー
CREATE POLICY "Business hours access" ON sensitive_data
    FOR ALL USING (
        EXTRACT(hour FROM current_time) BETWEEN 9 AND 17
        AND EXTRACT(dow FROM current_date) BETWEEN 1 AND 5
    );

-- 地域制限ポリシー
CREATE POLICY "Regional data access" ON customer_data
    FOR SELECT USING (
        region = current_setting('app.user_region', true)
        OR auth.jwt() ->> 'role' = 'global_admin'
    );

-- 階層型組織ポリシー
CREATE POLICY "Hierarchical access" ON employees
    FOR ALL USING (
        id = auth.uid() OR
        EXISTS (
            WITH RECURSIVE subordinates AS (
                SELECT id FROM employees WHERE manager_id = auth.uid()
                UNION ALL
                SELECT e.id FROM employees e
                JOIN subordinates s ON e.manager_id = s.id
            )
            SELECT 1 FROM subordinates WHERE id = employees.id
        )
    );

D.3 エラーコード・ステータスコード一覧

HTTP ステータスコード

  • 200 OK - 成功
  • 201 Created - リソース作成成功
  • 400 Bad Request - リクエスト不正
  • 401 Unauthorized - 認証エラー
  • 403 Forbidden - 認可エラー
  • 404 Not Found - リソース未発見
  • 409 Conflict - 競合エラー
  • 422 Unprocessable Entity - バリデーションエラー
  • 429 Too Many Requests - レート制限
  • 500 Internal Server Error - サーバーエラー

PostgreSQLエラーコード

  • 23505 - 一意制約違反
  • 23503 - 外部キー制約違反
  • 23514 - チェック制約違反
  • 42501 - 権限不足
  • 42P01 - テーブル未存在
  • 42703 - カラム未存在

Supabase固有エラー

  • PGRST301 - 単一行期待だが複数行
  • PGRST204 - 該当データなし
  • PGRST000 - PostgreSQLエラー

D.4 設定値リファレンス

PostgreSQL重要設定

-- 接続設定
max_connections = 100
superuser_reserved_connections = 3

-- メモリ設定
shared_buffers = 256MB
effective_cache_size = 1GB
work_mem = 4MB

-- WAL設定
wal_level = replica
max_wal_senders = 10
max_replication_slots = 10

-- 性能設定
random_page_cost = 1.1
effective_io_concurrency = 200

-- ログ設定
log_statement = 'mod'
log_min_duration_statement = 1000
log_line_prefix = '%t [%p]: [%l-1] user=%u,db=%d,app=%a,client=%h '

PostgREST設定

db-uri = "postgresql://user:pass@host:port/db"
db-schema = "public"
db-anon-role = "anon"
db-pool = 10
db-pool-timeout = 10

server-host = "0.0.0.0"
server-port = 3000

jwt-secret = "your-jwt-secret"
jwt-aud = "your-audience"

max-rows = 1000
pre-request = "public.check_permissions"

Edge Functions環境変数

SUPABASE_URL=https://your-project.supabase.co
SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-service-key

# 外部サービス
STRIPE_SECRET_KEY=sk_test_...
SENDGRID_API_KEY=SG....

# デバッグ設定
DENO_DEBUG=true
LOG_LEVEL=DEBUG

D.5 有用なSQLクエリ集

-- データベース使用量分析
SELECT 
    schemaname,
    tablename,
    pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) as total_size,
    pg_size_pretty(pg_relation_size(schemaname||'.'||tablename)) as table_size,
    pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename) - pg_relation_size(schemaname||'.'||tablename)) as index_size
FROM pg_tables 
WHERE schemaname = 'public'
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC;

-- インデックス効果分析
SELECT 
    schemaname,
    tablename,
    indexname,
    idx_scan,
    idx_tup_read,
    idx_tup_fetch,
    pg_size_pretty(pg_relation_size(indexrelid)) as index_size
FROM pg_stat_user_indexes 
ORDER BY idx_scan DESC;

-- アクティブな接続確認
SELECT 
    pid,
    usename,
    application_name,
    client_addr,
    state,
    query_start,
    query
FROM pg_stat_activity 
WHERE state != 'idle'
ORDER BY query_start;

-- RLS ポリシー一覧
SELECT 
    schemaname,
    tablename,
    policyname,
    permissive,
    roles,
    cmd,
    qual
FROM pg_policies 
WHERE schemaname = 'public'
ORDER BY tablename, policyname;

-- スロークエリ分析
SELECT 
    query,
    calls,
    total_time,
    mean_time,
    max_time,
    stddev_time
FROM pg_stat_statements 
WHERE calls > 100
ORDER BY mean_time DESC 
LIMIT 20;

終章

本教科書では、Supabaseを活用した3つの主要アーキテクチャパターンの実践的実装を通じて、モダンアプリケーション開発の包括的な知識を習得しました。

習得した主要スキル:

  • アーキテクチャパターンの適切な選択判断
  • セキュリティを考慮した認証・認可システムの設計
  • スケーラブルなデータベース設計とRLS実装
  • パフォーマンス最適化の体系的アプローチ
  • 運用自動化とトラブルシューティング手法

実践への応用: 本書で学んだパターンと手法は、Supabaseを使用するプロジェクトだけでなく、モダンなフルスタック開発全般に応用可能です。とくに、セキュリティファーストの設計思想と段階的な拡張戦略は、あらゆる規模のプロジェクトで価値を発揮します。

継続的な学習: 技術の進歩に合わせて、定期的に最新のベストプラクティスとSupabaseの新機能をキャッチアップし、本書の内容をアップデートしていくことをオススメします。

コミュニティへの貢献: 学んだ知識を活用して、オープンソースプロジェクトへの貢献や技術記事の執筆など、コミュニティへの還元も検討してください。


最新の情報とサンプルコードは、GitHubリポジトリで公開・更新しています。

Happy Coding with Supabase! 🚀


📝 付録活用まとめ

この付録で得られるもの

  • ✅ すぐに使える実践的な環境構築スクリプト・設定ファイル
  • ✅ 開発・運用時の体系的チェックリスト・ベストプラクティス
  • ✅ トラブル発生時の迅速な問題解決リファレンス
  • ✅ プロダクション対応の実装パターン・設定例

🎯 効果的な活用方法

| 場面 | 活用セクション | 期待効果 | |:—–|:————-|:———| | プロジェクト開始時 | A. 環境構築スクリプト | 迅速な開発環境準備 | | 実装中 | B. 設定テンプレート、C. コードスニペット | 効率的な実装・品質向上 | | 問題発生時 | G. トラブルシューティング | 素早い問題解決 | | 本番リリース前 | D. パフォーマンス、E. セキュリティ | 品質・安全性確保 |

🔄 継続的な改善

この付録は学習・実装の経験を積むことで、さらに価値が高まります:

  • 実際の開発で遭遇した課題・解決策の追記
  • チーム固有のベストプラクティスの蓄積
  • 新しいSupabase機能・コミュニティ情報の反映

📍 最終ナビゲーション