はじめに
データベースのマイグレーションは、アプリケーションへの影響が大きいため、実行タイミングを開発者がコントロールできることが重要です。本記事では、Vercel Hobby プランの制約を考慮しながら、Turso のデータベースブランチング機能と Drizzle ORM を組み合わせた堅牢なマイグレーション運用戦略を解説します。
Vercel Hobby プランの制約
Vercel Hobby プランでは、以下の環境のみ利用可能です:
| 環境 | ブランチ | ドメイン |
|---|---|---|
| Production | main | linto-dev.vercel.app |
| Preview | 全ての非 main ブランチ | プレビュー URL |
| Development | CLI 経由 | なし |
問題点: develop ブランチも issue-* ブランチも、すべて同じ「Preview」環境として扱われます。これでは、develop 用の永続データベースと issue 用の一時データベースを環境変数だけで区別できません。
解決策: 自動デプロイ無効化 + GitHub Actions 経由のデプロイ
本ガイドでは、以下の仕組みでこの問題を解決します:
- Turso グループトークン: 1 つのトークンで同グループ内の全データベースにアクセス
- 自動デプロイ無効化:
vercel.jsonのgit.deploymentEnabledで feature ブランチの自動デプロイを無効化 - Vercel CLI: GitHub Actions から Vercel CLI を使用して、環境変数設定後にビルド・デプロイを実行
- アプリケーション変更不要: 既存の
process.env.TURSO_DATABASE_URLをそのまま使用
アーキテクチャ概要
3 層データベース構成
| 環境 | データベース名 | ライフサイクル | マイグレーション |
|---|---|---|---|
| Production | linto-prod | 永続的 | 手動トリガー |
| Staging (develop) | linto-staging | 永続的 | develop マージ時に自動 |
| Issue Preview | linto-issue-{番号} | ブランチ単位で作成/削除 | PR 作成時に自動 |
トークン戦略
Turso ではグループトークンとデータベース固有トークンを使い分けることで、セキュリティと利便性を両立できます。
根拠(Turso 公式ドキュメントより):
"With these tools, you have the flexibility to create tokens for a single database or all databases within a group."
| トークン種別 | アクセス範囲 | 用途 |
|---|---|---|
| DB 固有トークン | 単一 DB のみ | Production(最小権限) |
| グループトークン | グループ内全 DB | Preview(運用効率) |
これにより、環境変数で変更が必要なのは TURSO_DATABASE_URL のみとなり、Preview 環境ではトークンを共通で使用できます。
マイグレーション戦略
本ガイドでは、すべての環境で drizzle-kit generate + drizzle-kit migrate によるバージョン管理されたマイグレーションを採用します。
npx drizzle-kit generate # マイグレーションファイル生成
npx drizzle-kit migrate # マイグレーション適用採用理由:
- SQL マイグレーションファイルをバージョン管理できる
- 変更履歴の追跡が可能
- ロールバック計画が立てやすい
- チームでのコードレビューが可能
- ローカル・PR プレビュー・Staging・Production で一貫した手法を使用
事前準備
1. Turso グループとデータベースの作成
セキュリティ上の理由から、本番環境とプレビュー環境は別のグループに分離します。
Turso 公式ドキュメントより:
"With these tools, you have the flexibility to create tokens for a single database or all databases within a group."
グループトークンは同グループ内の全データベースにアクセスできます。そのため、本番とプレビューを同じグループに配置すると、プレビュー環境のトークンが漏洩した場合に本番データベースも危険にさらされます。
# Production グループの作成(本番専用)
turso group create production --location nrt
# Preview グループの作成(Staging + Feature ブランチ用)
turso group create preview --location nrt
# データベースの作成
turso db create linto-prod --group production
turso db create linto-staging --group preview2. トークンの取得
# Production 用トークン(本番 DB 専用)
turso db tokens create linto-prod --expiration none
# Preview グループトークン(Staging + Feature DB 用)
turso group tokens create preview
# Platform API 用トークン(GitHub Actions での DB 作成/削除用)
turso auth api-tokens mint github-actionsトークン分離の利点:
| トークン | アクセス範囲 | 用途 |
|---|---|---|
| Production DB Token | linto-prod のみ | Vercel Production 環境 |
| Preview Group Token | linto-staging + 全 feature DB | Vercel Preview 環境 |
| Platform API Token | DB の作成/削除操作 | GitHub Actions |
3. vercel.json の設定(自動デプロイの制御)
issue ブランチの自動デプロイを無効化し、GitHub Actions からのデプロイのみを許可します。
Vercel 公式ドキュメントより:
"The
git.deploymentEnabledconfiguration option allows you to specify which branches should not trigger a deployment upon commits."
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"git": {
"deploymentEnabled": {
"main": true,
"develop": true,
"issue-*": false
}
}
}| ブランチパターン | 自動デプロイ | 理由 |
|---|---|---|
main | ✅ 有効 | 環境変数は Vercel ダッシュボードで事前設定済み |
develop | ✅ 有効 | 環境変数は Vercel ダッシュボードで事前設定済み |
issue-* | ❌ 無効 | GitHub Actions 経由でデプロイ(DB 作成後) |
4. GitHub Secrets の設定
| シークレット名 | 説明 |
|---|---|
TURSO_API_TOKEN | Turso Platform API トークン |
TURSO_ORG_NAME | Turso 組織名 |
TURSO_PROD_DB_TOKEN | Production DB 専用トークン |
TURSO_GROUP_TOKEN | Preview グループトークン |
TURSO_STAGING_DB_URL | Staging DB の URL |
TURSO_PROD_DB_URL | Production DB の URL |
VERCEL_TOKEN | Vercel API トークン |
VERCEL_ORG_ID | Vercel 組織 ID(vercel link 後に .vercel/project.json で確認可能) |
VERCEL_PROJECT_ID | Vercel プロジェクト ID |
5. Vercel 環境変数の設定
Vercel ダッシュボードで以下のように設定します。
Production 環境(main ブランチ)
| 変数名 | 値 | 環境 |
|---|---|---|
TURSO_DATABASE_URL | libsql://linto-prod-{org}.turso.io | Production |
TURSO_AUTH_TOKEN | {Production DB Token} | Production |
重要: Production 環境ではデータベース固有のトークンを使用します。これにより、万が一 Preview 環境のトークンが漏洩しても、本番データベースは保護されます。
Preview 環境(develop ブランチ専用)
Vercel では Preview 環境にブランチ固有の環境変数を設定できます:
- Vercel ダッシュボード → Settings → Environment Variables
- 新しい変数を追加
- Environment: Preview を選択
- "Add to specific Git Branch" をクリック
- ブランチ名に
developを入力
| 変数名 | 値 | 環境 | ブランチ |
|---|---|---|---|
TURSO_DATABASE_URL | libsql://linto-staging-{org}.turso.io | Preview | develop |
TURSO_AUTH_TOKEN | {Preview Group Token} | Preview | develop |
Preview 環境(issue ブランチ用)
issue ブランチの TURSO_DATABASE_URL は GitHub Actions が Vercel API 経由で自動設定します(後述のワークフロー参照)。
共通で必要な設定:
| 変数名 | 値 | 環境 |
|---|---|---|
TURSO_AUTH_TOKEN | {Preview Group Token} | Preview |
ポイント:
developブランチ固有の変数が、デフォルトの Preview 変数を上書きします- issue ブランチの
TURSO_DATABASE_URLは PR 作成時に GitHub Actions が自動設定 - PR クローズ時に GitHub Actions が自動削除
- Preview Group Token は
previewグループ内の全 DB(staging + issue)にアクセス可能ですが、productionグループの DB にはアクセスできません
アプリケーション側の変更
変更不要
Vercel API のブランチ固有環境変数機能を活用することで、アプリケーション側のコード変更は一切不要です。
Vercel 公式ドキュメントより:
{
"key": "TURSO_DATABASE_URL",
"value": "libsql://linto-issue-1-org.turso.io",
"target": ["preview"],
"gitBranch": "issue-1"
}
gitBranchパラメータを指定することで、特定のブランチ専用の環境変数を設定できます。
この仕組みにより:
- Production (main): Vercel ダッシュボードで設定した
TURSO_DATABASE_URLを使用 - Staging (develop): ブランチ固有の環境変数で
linto-stagingに接続 - issue ブランチ: GitHub Actions が Vercel API 経由でブランチ固有の環境変数を自動設定
既存の src/db/index.ts は変更せず、単純に process.env.TURSO_DATABASE_URL を参照するだけで OK です。
Google OAuth のプレビュー環境対応
問題点
Google OAuth では、Authorized redirect URIs に事前登録した URL にしかコールバックできません。しかし、Vercel のプレビュー環境では以下のような動的な URL が生成されます:
https://linto-dev-abc123-username.vercel.apphttps://linto-dev-feature-login-xyz.vercel.app
これらすべてを Google Cloud Console に事前登録することは不可能です。
解決策: Better Auth の oAuthProxy プラグイン
Better Auth には oAuthProxy プラグインが用意されており、この問題を解決できます。
Better Auth 公式ドキュメントより:
"A proxy plugin, that allows you to proxy OAuth requests. Useful for development and preview deployments where the redirect URL can't be known in advance to add to the OAuth provider."
動作原理
実装方法
1. src/lib/auth.ts を更新:
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { oAuthProxy } from "better-auth/plugins";
import { db } from "@/db";
import { env } from "@/env";
export const auth = betterAuth({
baseURL: env.BETTER_AUTH_URL,
database: drizzleAdapter(db, {
provider: "sqlite",
}),
plugins: [
oAuthProxy({
// Production 環境の URL(Google に登録した redirect URI のベース)
productionURL: "https://linto-dev.vercel.app",
}),
],
socialProviders: {
google: {
clientId: env.GOOGLE_CLIENT_ID,
clientSecret: env.GOOGLE_CLIENT_SECRET,
// redirect URI を明示的に Production に設定
redirectURI: "https://linto-dev.vercel.app/api/auth/callback/google",
},
},
});
export type Auth = typeof auth;
export type Session = typeof auth.$Infer.Session;2. Google Cloud Console の設定:
Google Cloud Console の Authorized redirect URIs には、Production URL のみを登録します:
https://linto-dev.vercel.app/api/auth/callback/googlePreview 環境や localhost の URL は登録不要です。
3. 環境変数の設定:
各環境で BETTER_AUTH_URL を適切に設定します:
| 環境 | BETTER_AUTH_URL |
|---|---|
| Production (main) | https://linto-dev.vercel.app |
| Preview (develop) | https://linto-dev-git-develop-{username}.vercel.app |
| Preview (issue) | https://linto-dev-{branch}-{username}.vercel.app |
| Local | http://localhost:3000 |
ポイント: BETTER_AUTH_URL は現在のデプロイ URL を設定します。oAuthProxy プラグインがこれを検出し、Production 以外の場合はプロキシ経由で認証を行います。
Vercel での環境変数設定
Vercel では VERCEL_URL システム環境変数を使用して動的に BETTER_AUTH_URL を設定できます:
// src/env.ts
export const env = createEnv({
server: {
// Vercel では VERCEL_URL から自動構築、ローカルでは明示的に設定
BETTER_AUTH_URL: z.string().url().default(
process.env.VERCEL_URL
? `https://${process.env.VERCEL_URL}`
: "http://localhost:3000"
),
// ...
},
});または、Vercel の Environment Variables で:
| 変数名 | 値 | 環境 |
|---|---|---|
BETTER_AUTH_URL | https://linto-dev.vercel.app | Production |
BETTER_AUTH_URL | https://$VERCEL_URL | Preview |
セキュリティ上の注意
oAuthProxy は以下のセキュリティ機能を提供します:
- Cookie の暗号化: Production サーバーでセッション Cookie を暗号化してから Preview 環境に転送
- Secret の共有:
BETTER_AUTH_SECRETが全環境で同じである必要がある - オリジン検証: 信頼できるオリジンからのリクエストのみを受け入れる
重要: BETTER_AUTH_SECRET は全環境(Production, Preview, Local)で同じ値を使用してください。異なる値だと Cookie の復号化に失敗します。
GitHub Actions ワークフロー
1. issue ブランチ用データベース作成 + Vercel デプロイ
Vercel 公式ドキュメントより:
"Use
vercel buildinside GitHub Actions to build your project without exposing source code to Vercel, and finallyvercel deploy --prebuiltto skip the build step on Vercel."
.github/workflows/preview-deploy.yml:
name: Preview Deploy
on:
pull_request:
types: [opened, reopened, synchronize]
branches: [develop]
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
jobs:
deploy-preview:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Install Turso CLI
run: curl -sSfL https://get.tur.so/install.sh | bash
- name: Install Vercel CLI
run: npm install -g vercel@latest
# ========================================
# Step 1: データベースの作成(初回のみ)
# ========================================
- name: Generate DB name from branch
id: db_name
run: |
BRANCH="${{ github.head_ref }}"
DB_NAME="linto-$(echo "$BRANCH" | tr '[:upper:]' '[:lower:]' | tr -cd '[:alnum:]-' | cut -c 1-32)"
echo "name=$DB_NAME" >> $GITHUB_OUTPUT
- name: Check if database exists
id: db_check
env:
TURSO_API_TOKEN: ${{ secrets.TURSO_API_TOKEN }}
run: |
if ~/.turso/turso db show ${{ steps.db_name.outputs.name }} > /dev/null 2>&1; then
echo "exists=true" >> $GITHUB_OUTPUT
else
echo "exists=false" >> $GITHUB_OUTPUT
fi
- name: Create Preview Database (branched from staging)
if: steps.db_check.outputs.exists == 'false'
env:
TURSO_API_TOKEN: ${{ secrets.TURSO_API_TOKEN }}
run: |
~/.turso/turso db create ${{ steps.db_name.outputs.name }} \
--from-db linto-staging \
--group preview
- name: Get Preview Database URL
id: db_url
env:
TURSO_API_TOKEN: ${{ secrets.TURSO_API_TOKEN }}
run: |
DB_URL=$(~/.turso/turso db show ${{ steps.db_name.outputs.name }} --url)
echo "url=$DB_URL" >> $GITHUB_OUTPUT
# ========================================
# Step 2: マイグレーションの適用
# ========================================
- name: Apply migrations
env:
TURSO_DATABASE_URL: ${{ steps.db_url.outputs.url }}
TURSO_AUTH_TOKEN: ${{ secrets.TURSO_GROUP_TOKEN }}
run: npx drizzle-kit migrate
# ========================================
# Step 3: Vercel 環境変数の設定
# ========================================
- name: Set Vercel environment variable for branch
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
run: |
curl -X POST "https://api.vercel.com/v10/projects/$VERCEL_PROJECT_ID/env?upsert=true" \
-H "Authorization: Bearer $VERCEL_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"key": "TURSO_DATABASE_URL",
"value": "${{ steps.db_url.outputs.url }}",
"type": "encrypted",
"target": ["preview"],
"gitBranch": "${{ github.head_ref }}"
}'
# ========================================
# Step 4: Vercel CLI でビルド & デプロイ
# ========================================
- name: Pull Vercel Environment
run: vercel pull --yes --environment=preview --git-branch=${{ github.head_ref }} --token=${{ secrets.VERCEL_TOKEN }}
- name: Build Project
run: vercel build --token=${{ secrets.VERCEL_TOKEN }}
env:
TURSO_DATABASE_URL: ${{ steps.db_url.outputs.url }}
TURSO_AUTH_TOKEN: ${{ secrets.TURSO_GROUP_TOKEN }}
- name: Deploy to Vercel
id: deploy
run: |
DEPLOY_URL=$(vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }})
echo "url=$DEPLOY_URL" >> $GITHUB_OUTPUT
# ========================================
# Step 5: PR にコメント
# ========================================
- name: Comment PR with deployment info
if: github.event.action == 'opened'
uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## 🚀 Preview Deployed
| Item | Value |
|------|-------|
| Preview URL | ${{ steps.deploy.outputs.url }} |
| Database | \`${{ steps.db_name.outputs.name }}\` |
| Branch | \`${{ github.head_ref }}\` |
✅ Database created from \`linto-staging\`
✅ Migrations applied
✅ Deployed with correct environment variables
**Note**: Database will be deleted when PR is closed.
`
})ポイント:
synchronizeイベント: PR への追加 push でも自動デプロイ- DB 存在チェック: 2 回目以降は DB 作成をスキップ
- Vercel CLI:
vercel pull→vercel build→vercel deploy --prebuiltの順で実行 - 環境変数設定後にビルド: 競合状態なし、再デプロイ不要
2. PR クローズ時のデータベース削除
.github/workflows/preview-db-delete.yml:
name: Delete Preview Database
on:
pull_request:
types: [closed]
branches: [develop]
jobs:
delete-preview-db:
runs-on: ubuntu-latest
steps:
- name: Install Turso CLI
run: curl -sSfL https://get.tur.so/install.sh | bash
- name: Generate DB name from branch
id: db_name
run: |
BRANCH="${{ github.head_ref }}"
DB_NAME="linto-$(echo "$BRANCH" | tr '[:upper:]' '[:lower:]' | tr -cd '[:alnum:]-' | cut -c 1-32)"
echo "name=$DB_NAME" >> $GITHUB_OUTPUT
- name: Delete Preview Database
env:
TURSO_API_TOKEN: ${{ secrets.TURSO_API_TOKEN }}
run: |
~/.turso/turso db destroy ${{ steps.db_name.outputs.name }} --yes || true
- name: Get Vercel environment variable ID
id: env_id
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
VERCEL_TEAM_ID: ${{ secrets.VERCEL_TEAM_ID }}
BRANCH_NAME: ${{ github.head_ref }}
run: |
TEAM_PARAM=""
if [ -n "$VERCEL_TEAM_ID" ]; then
TEAM_PARAM="?teamId=$VERCEL_TEAM_ID"
fi
# ブランチ固有の環境変数を検索
# 注意: jq 内で環境変数を使用するため --arg を使用
ENV_ID=$(curl -s "https://api.vercel.com/v9/projects/$VERCEL_PROJECT_ID/env$TEAM_PARAM" \
-H "Authorization: Bearer $VERCEL_TOKEN" \
| jq -r --arg branch "$BRANCH_NAME" '.envs[] | select(.key == "TURSO_DATABASE_URL" and .gitBranch == $branch) | .id')
echo "id=$ENV_ID" >> $GITHUB_OUTPUT
- name: Delete Vercel environment variable
if: steps.env_id.outputs.id != ''
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
VERCEL_TEAM_ID: ${{ secrets.VERCEL_TEAM_ID }}
run: |
TEAM_PARAM=""
if [ -n "$VERCEL_TEAM_ID" ]; then
TEAM_PARAM="?teamId=$VERCEL_TEAM_ID"
fi
curl -X DELETE "https://api.vercel.com/v9/projects/$VERCEL_PROJECT_ID/env/${{ steps.env_id.outputs.id }}$TEAM_PARAM" \
-H "Authorization: Bearer $VERCEL_TOKEN"
echo "Vercel environment variable deleted for branch: ${{ github.head_ref }}"
- name: Comment PR
uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## Preview Database Deleted
The preview database \`${{ steps.db_name.outputs.name }}\` and its Vercel environment variable have been deleted.
`
})3. Staging(develop)へのマイグレーション
.github/workflows/staging-migration.yml:
name: Staging Migration
on:
push:
branches: [develop]
paths:
- "src/db/schema/**"
- "drizzle/**"
jobs:
migrate-staging:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Run migrations on Staging
env:
TURSO_DATABASE_URL: ${{ secrets.TURSO_STAGING_DB_URL }}
TURSO_AUTH_TOKEN: ${{ secrets.TURSO_GROUP_TOKEN }}
run: npx drizzle-kit migrate
- name: Notify success
if: success()
run: echo "Staging migration completed successfully"4. Production マイグレーション(手動トリガー)
.github/workflows/production-migration.yml:
name: Production Migration
on:
workflow_dispatch:
inputs:
confirm:
description: 'Type "migrate-production" to confirm'
required: true
type: string
dry_run:
description: "Dry run (show pending migrations without applying)"
required: false
type: boolean
default: true
jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: Validate confirmation
if: github.event.inputs.confirm != 'migrate-production'
run: |
echo "::error::Confirmation text does not match. Please type 'migrate-production' to proceed."
exit 1
migrate-production:
needs: validate
runs-on: ubuntu-latest
environment: production
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: main
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Check pending migrations
env:
TURSO_DATABASE_URL: ${{ secrets.TURSO_PROD_DB_URL }}
TURSO_AUTH_TOKEN: ${{ secrets.TURSO_PROD_DB_TOKEN }}
run: |
echo "Checking for pending migrations..."
npx drizzle-kit check
- name: Show pending migrations (if dry run)
if: github.event.inputs.dry_run == 'true'
run: |
echo "=== DRY RUN MODE ==="
echo "Pending migration files:"
ls -la drizzle/*.sql 2>/dev/null || echo "No migration files found"
echo ""
echo "Migration journal:"
cat drizzle/meta/_journal.json 2>/dev/null || echo "No journal found"
echo "=== END DRY RUN ==="
- name: Run migrations on Production
if: github.event.inputs.dry_run == 'false'
env:
TURSO_DATABASE_URL: ${{ secrets.TURSO_PROD_DB_URL }}
TURSO_AUTH_TOKEN: ${{ secrets.TURSO_PROD_DB_TOKEN }}
run: |
echo "Running production migrations..."
npx drizzle-kit migrate運用フロー図
技術的根拠
処理順序とタイミングの重要性
Vercel 公式ドキュメントより:
"Changes to environment variables are not applied to previous deployments, they only apply to new deployments. You must redeploy your project to update the value of any variables you change in the deployment."
自動デプロイを有効にしたまま環境変数を後から設定すると、競合状態が発生します。本ガイドでは、以下の方法でこの問題を解決します:
解決策: 自動デプロイ無効化 + Vercel CLI
Vercel 公式ドキュメントより:
"The
git.deploymentEnabledconfiguration option allows you to specify which branches should not trigger a deployment upon commits."
"Use
vercel buildinside GitHub Actions to build your project without exposing source code to Vercel, and finallyvercel deploy --prebuiltto skip the build step on Vercel."
ポイント:
- 自動デプロイ無効: issue ブランチは
vercel.jsonで自動デプロイを無効化 - 順序保証: GitHub Actions が DB 作成 → マイグレーション → 環境変数設定 → ビルド → デプロイの順序を保証
- 単一デプロイ: 再デプロイ不要、効率的
1. Vercel ブランチ固有環境変数
Vercel 公式ドキュメントより:
"Preview environment variables are applied to deployments from any Git branch that does not match the Production Branch. When you add a preview environment variable, you can choose to apply it to all non-production branches or select a specific branch. Any branch-specific variables will override other preview environment variables with the same name."
これにより、develop ブランチ専用の環境変数を設定でき、他の feature ブランチとは異なるデータベースに接続できます。
2. Turso グループ分離とトークン戦略
Turso 公式ドキュメントより:
"With these tools, you have the flexibility to create tokens for a single database or all databases within a group."
グループトークンは同グループ内の全データベースにアクセスできるため、セキュリティ上の理由から Production と Preview を別グループに分離します:
| グループ | 含まれる DB | トークン | リスク範囲 |
|---|---|---|---|
production | linto-prod のみ | DB 固有トークン | 本番 DB のみ |
preview | linto-staging + 全 issue DB | グループトークン | Preview 環境のみ |
分離の利点:
- Preview 環境のトークンが漏洩しても、本番 DB にはアクセス不可
- Preview グループ内では 1 つのトークンで全 DB にアクセス可能(管理が簡素化)
- 本番 DB は独立したトークンで厳重に保護
3. Vercel API ブランチ固有環境変数
Vercel 公式ドキュメントより:
"
gitBranch- The git branch name for this Environment Variable to be associated with. This property can be used to create environment variables that are specific to a git branch."
Vercel API の POST /v10/projects/{idOrName}/env エンドポイントでは、gitBranch パラメータを指定することで特定のブランチ専用の環境変数を設定できます:
{
"key": "TURSO_DATABASE_URL",
"value": "libsql://linto-issue-1-org.turso.io",
"type": "encrypted",
"target": ["preview"],
"gitBranch": "issue-1"
}これにより:
- アプリケーション側の変更不要: 既存の
process.env.TURSO_DATABASE_URLをそのまま使用 - GitHub Actions で自動化: PR 作成時に環境変数を自動設定、PR クローズ時に自動削除
- セキュリティ:
type: "encrypted"でトークンを暗号化して保存
4. Better Auth oAuthProxy プラグイン
Better Auth 公式ドキュメントより:
"A proxy plugin, that allows you to proxy OAuth requests. Useful for development and preview deployments where the redirect URL can't be known in advance to add to the OAuth provider."
oAuthProxy プラグインにより:
- Google OAuth の redirect URI は Production URL のみ登録すれば OK
- Preview/Development 環境では Production 経由でプロキシ認証
- Cookie は暗号化されて転送されるためセキュリティも確保
- クライアント側のコード変更は不要
ベストプラクティス
1. マイグレーションファイルの管理
# マイグレーションファイルの生成
npx drizzle-kit generate
# 生成されたファイルを確認
ls drizzle/
# コミットに含める
git add drizzle/
git commit -m "Add migration: add user profile fields"2. 破壊的変更への対処
カラム削除やテーブル削除などの破壊的変更は、2 段階で実行:
Phase 1: 準備
// 1. アプリケーションコードから該当カラムの参照を削除
// 2. デプロイPhase 2: マイグレーション
// 3. カラム削除のマイグレーションを実行3. ロールバック手順の文書化
各マイグレーションに対して、ロールバック SQL を用意:
-- drizzle/0001_add_profile.sql
ALTER TABLE users ADD COLUMN profile_image TEXT;
-- drizzle/0001_add_profile_rollback.sql (手動で作成)
ALTER TABLE users DROP COLUMN profile_image;4. マイグレーション前のバックアップ
# production-migration.yml に追加
- name: Create backup before migration
env:
TURSO_API_TOKEN: ${{ secrets.TURSO_API_TOKEN }}
run: |
BACKUP_NAME="linto-prod-backup-$(date +%Y%m%d-%H%M%S)"
# バックアップは preview グループに作成(本番トークンのスコープを限定)
~/.turso/turso db create $BACKUP_NAME --from-db linto-prod --group preview
echo "Backup created: $BACKUP_NAME"注意: バックアップを preview グループに作成することで、Preview 環境のトークンでバックアップにアクセスできます。本番グループに作成すると、本番トークンのスコープが広がるリスクがあります。
トラブルシューティング
issue ブランチのデータベースが見つからない
- GitHub Actions のログで DB 作成が成功しているか確認
- ブランチ名の sanitize ロジックを確認(特殊文字、長さ制限)
- Turso ダッシュボードでデータベースの存在を確認
develop ブランチが issue 用 DB に接続してしまう
- Vercel のブランチ固有環境変数が正しく設定されているか確認
developブランチにTURSO_DATABASE_URLが設定されているか確認- 環境変数の優先順位を確認(ブランチ固有 > Preview デフォルト)
マイグレーションが失敗する
npx drizzle-kit checkでスキーマの整合性を確認- ローカルで
npx drizzle-kit generateを実行し、生成されるマイグレーションファイルを確認 - 依存関係のあるテーブルの順序を確認
- マイグレーションファイルの SQL 構文を確認
まとめ
Vercel Hobby プランの制約下でも、以下の方法で develop ブランチと issue ブランチを区別できます:
| 環境 | ブランチ | データベース | デプロイ方法 |
|---|---|---|---|
| Production | main | linto-prod | Vercel 自動デプロイ |
| Staging | develop | linto-staging | Vercel 自動デプロイ |
| Issue Preview | issue-* | linto-issue-{番号} | GitHub Actions + Vercel CLI |
キーポイント:
- 自動デプロイ制御:
vercel.jsonのgit.deploymentEnabledで issue ブランチの自動デプロイを無効化 - Vercel CLI: GitHub Actions から
vercel build+vercel deploy --prebuiltで順序を完全制御 - 競合状態の回避: 環境変数設定後にビルド・デプロイを実行するため、再デプロイ不要
- グループ分離: Production と Preview を別グループに分離してセキュリティを確保
- アプリケーション変更不要: 既存の
process.env.TURSO_DATABASE_URLをそのまま使用
この構成により、再デプロイなし・アプリケーション変更なしで、Hobby プランでも本格的なマイグレーション運用が可能になります。