CI/CD

Vercel CLI デプロイ時の環境変数問題と解決策

GitHub Actions から Vercel CLI でデプロイする際に、ブランチ固有の環境変数が適用されない問題の原因と解決策を詳しく解説します。

はじめに

Turso + Drizzle のマイグレーション運用環境を構築する過程で、Vercel CLI を使用したデプロイ時に環境変数が適用されないという問題に遭遇しました。本記事では、この問題の根本原因と解決策を詳しく解説します。


構成の概要

目標

  • issue-* ブランチの自動デプロイを無効化
  • GitHub Actions 経由で DB 作成 → マイグレーション → 環境変数設定 → デプロイの順序を制御
  • ブランチごとに異なる Turso データベースに接続

採用したアプローチ


遭遇した問題

エラー内容

デプロイは成功するが、ランタイムで以下のエラーが発生:

❌ Invalid environment variables: [
  {
    expected: 'string',
    code: 'invalid_type',
    path: [ 'TURSO_DATABASE_URL' ],
    message: 'Invalid input: expected string, received undefined'
  }
]

調査結果

  1. Vercel の環境変数は設定されていた
$ vercel env ls
TURSO_DATABASE_URL   Encrypted   Preview (issue-27)   30m ago
  1. ブランチ固有の環境変数も正しく取得できた
$ vercel env pull --environment=preview --git-branch=issue-27 .env.local
TURSO_DATABASE_URL="libsql://linto-issue-27-naokiyazawa.aws-ap-northeast-1.turso.io"
  1. デプロイメントにはブランチ情報がなかった
$ vercel inspect https://linto-xxx.vercel.app
target preview
# ブランチ情報が表示されない

根本原因

原因 1: vercel.json の読み込みタイミング

vercel.jsongit.deploymentEnabled 設定は、Vercel プロジェクトのデフォルトブランチ(main)から読み込まれます

{
  "git": {
    "deploymentEnabled": {
      "main": true,
      "develop": true,
      "issue-*": false
    }
  }
}

この設定は issue-27 ブランチにあるだけでは機能せず、main または develop にマージされるまで Vercel に認識されません。

原因 2: Vercel CLI デプロイとブランチ固有環境変数

Vercel 公式ドキュメントによると、環境変数の gitBranch パラメータは特定のブランチに紐づけるためのものです:

{
  "key": "TURSO_DATABASE_URL",
  "value": "libsql://...",
  "target": ["preview"],
  "gitBranch": "issue-27"
}

しかし、この仕組みは Git 連携(自動デプロイ)経由のデプロイにのみ適用されることが判明しました。

vercel deploy --prebuilt は Git 連携を使用しないため、gitBranch で設定した環境変数が適用されません。

検証: --meta gitBranch の効果

vercel deploy --prebuilt --meta gitBranch=issue-27 --token=***

--meta gitBranch を追加してもメタデータとして設定されるだけで、環境変数の選択には影響しません

$ vercel ls --meta gitBranch=issue-27
# デプロイメントは表示される(メタデータは設定されている)
# しかしランタイムで環境変数は undefined

解決策

--env オプションで直接環境変数を渡す

vercel deploy コマンドには --env オプションがあり、デプロイ時にランタイム環境変数を直接設定できます。

Vercel CLI ドキュメントより:

-e, --env <KEY=VALUE>  Specify environment variables during run-time
                       (e.g. `-e KEY1=value1 -e KEY2=value2`)

修正後のワークフロー

- name: Deploy to Vercel
  id: deploy
  run: |
    DEPLOY_URL=$(vercel deploy --prebuilt \
      --env TURSO_DATABASE_URL=${{ steps.db_url.outputs.url }} \
      --env TURSO_AUTH_TOKEN=${{ secrets.TURSO_GROUP_TOKEN }} \
      --meta gitBranch=${{ github.head_ref }} \
      --token=${{ secrets.VERCEL_TOKEN }})
    echo "url=$DEPLOY_URL" >> $GITHUB_OUTPUT

なぜこれが機能するのか

方法動作結果
Vercel API で gitBranch 設定Git 連携デプロイにのみ適用CLI デプロイでは ❌
--meta gitBranchメタデータとして保存環境変数には影響なし ❌
--env KEY=VALUEデプロイ時に直接設定確実に適用 ✅

その他の問題と対処

問題: staging-migration の paths 設定

元の設定:

on:
  push:
    branches: [develop]
    paths:
      - "src/db/schema/**"
      - "drizzle/**"

問題点:

src/db/schema/** を含めると、スキーマを変更しただけで(マイグレーションファイルを生成せずに)ワークフローが実行されてしまいます。

Drizzle のマイグレーションワークフロー:

src/db/schema/** 変更

drizzle-kit generate

drizzle/** にマイグレーションファイル生成

git commit & push

drizzle-kit migrate(DB に適用)

drizzle-kit migrate はマイグレーションファイルを読み取って DB に適用するため、drizzle/** の変更のみをトリガーにすれば十分です。

修正後:

on:
  push:
    branches: [develop]
    paths:
      - "drizzle/**"

最終的なワークフロー構成

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@v6

      - name: Setup Node.js
        uses: actions/setup-node@v6
        with:
          node-version: "24"
          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 }}
          BETTER_AUTH_SECRET: ${{ secrets.BETTER_AUTH_SECRET }}
          BETTER_AUTH_URL: "http://localhost:3000"
          GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}
          GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }}
          NEXT_PUBLIC_SITE_URL: "http://localhost:3000"
        run: npm run db: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 \
            --env TURSO_DATABASE_URL=${{ steps.db_url.outputs.url }} \
            --env TURSO_AUTH_TOKEN=${{ secrets.TURSO_GROUP_TOKEN }} \
            --meta gitBranch=${{ github.head_ref }} \
            --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.
              `
            })

学んだこと

1. Vercel CLI と Git 連携の違い

機能Git 連携(自動デプロイ)Vercel CLI
ブランチ検出自動手動(--meta
ブランチ固有環境変数自動適用適用されない
環境変数設定ダッシュボード/API--env オプション

2. vercel.json の読み込みタイミング

vercel.json はデフォルトブランチから読み込まれるため、feature ブランチにあるだけでは設定が反映されません。

3. --env オプションの重要性

CLI デプロイでは、--env オプションを使用して直接環境変数を渡すのが最も確実な方法です。


まとめ

Vercel CLI を使用して GitHub Actions からデプロイする場合、ブランチ固有の環境変数は自動的には適用されません。--env オプションを使用して、デプロイ時に直接環境変数を渡すことで、この問題を解決できます。

vercel deploy --prebuilt \
  --env TURSO_DATABASE_URL=$DB_URL \
  --env TURSO_AUTH_TOKEN=$TOKEN \
  --token=$VERCEL_TOKEN

この方法により、Vercel プロジェクトの環境変数設定に依存せず、確実に正しい環境変数でデプロイできます。