型別生成與 CI/CD:讓 GitHub Actions 自動守住型別與部署 | Supabase 完整教學
這是 Supabase CLI 段的收尾。前面幾篇我們把 schema 變更變成帶時間戳的遷移檔、打通了
db push的單向部署,又用 Branching 讓每個 PR 都有隔離的預覽環境。本篇要做的,是把「型別生成、遷移部署、函式上線」這些重複、機械、該自動跑的事,全部交給 GitHub Actions:用supabase gen types typescript自動生成 TypeScript 型別、再以git diff --exit-code在 CI 裡守住型別與 schema 的同步;用supabase/setup-cli搭配SUPABASE_ACCESS_TOKEN,自動跑db lint、db push部署遷移、functions deploy上線 Edge Functions,並實作 staging → production 的兩段式部署。走完這篇,你的整條後端工作流就會跑成一台不用人顧的機器。
前言
先一句話定義本篇要打造的東西:這是一條「型別自動同步 + 遷移自動部署」的 CI/CD(持續整合/持續部署)流水線——每次有人開 PR、每次 merge 進主要分支,GitHub Actions 都會自動幫你檢查型別有沒有跟上 schema、把遷移套到正確的環境、把 Edge Functions 部署上線,全程不需要人手動去 db push 或記得「要生成型別」。
用一個類比理解它的價值。如果說遷移檔是「你想對資料庫做的每一筆變更的書面提案」,那 CI/CD 就是「自動化的審查與施工團隊」。 過去這些事是人工的:改完 schema,你得記得重新生成型別、記得 commit、記得連到正確的專案、記得 db push、記得部署函式——任何一步漏掉,就可能是「前端拿著過期型別」或「忘了推遷移導致線上壞掉」。CI/CD 把這整套變成:提案(PR)一送出,機器就自動檢查、自動施工,而且每一步都留下 git 紀錄可稽核。
而這一切能自動化的地基,正是前幾篇打下的:遷移檔(schema 的真相來源)、gen types(從 schema 生型別)、db push(部署遷移)、functions deploy(部署函式)——本篇不引入新指令,而是把這些既有指令編排進 GitHub Actions 的 workflow,讓它們在對的時機自動跑起來。
本篇你會學到:
- 型別自動同步的守門機制:用
gen types+git diff --exit-code在 CI 裡強制「schema 一改、型別必須跟著更新並提交」 - GitHub Actions 部署遷移與函式:用
supabase/setup-cli+SUPABASE_ACCESS_TOKEN,自動跑db lint、db push、functions deploy - staging → production 兩段式部署:develop 分支自動部署 staging、main 分支自動部署 production,並用
--dry-run與審核關卡把關 - 與 Branching 搭配:Branching 管開發期預覽、CI/CD 管合併後推進,兩者如何接成一條完整流水線
本篇聚焦「型別生成與 CI/CD」。CLI 安裝、本機環境、遷移工作流、Branching 已在前幾篇講透,這裡假設你已經能在本機
supabase start、用migration new/db diff產生遷移、也理解 Branching 的預覽環境。CI/CD 的實際 action 版本、CLI 旗標、計費與權限細節都可能隨版本調整,正式導入前一律以官方文件為準。
核心概念
在寫 workflow 之前,先建立兩張心智圖:一是「型別如何自動同步、CI 如何守門」,二是「遷移與函式如何依分支自動部署到不同環境」。
一、型別自動同步:生成不是重點,守門才是
supabase gen types typescript 能從 PostgreSQL 的 schema 自動生成 TypeScript 型別定義(database.types.ts),讓前端拿到完整的型別安全與自動補全。但真正棘手的問題不是「怎麼生成」,而是「怎麼確保這份型別檔永遠和 schema 同步」——只要有人改了 schema 卻忘了重新生成並 commit,前端就會拿著過期的型別寫程式,直到執行期才炸掉。
CI 的解法很聰明:在乾淨環境重新生成一次型別,然後用 git diff 比對它和 repo 裡那份是否一致。 流程長這樣:
① supabase db reset 在乾淨環境套用所有遷移,得到最新 schema
② supabase gen types ... --local > database.types.ts
從這個 schema 生成型別,覆蓋 repo 裡的那份
③ git diff --exit-code database.types.ts
比對:有差異就以非 0 退出、CI 失敗
關鍵在第 ③ 步的 --exit-code:
- 若沒有差異(git diff 為空、exit code = 0):代表你 commit 的型別檔和「從最新 schema 生成的版本」一致 → CI 通過。
- 若有差異(exit code ≠ 0):代表有人改了 schema 卻沒更新型別檔 → CI 失敗,擋在 merge 之前。
換句話說,這一步的價值是「守門」不是「生成」:它把「忘了更新型別」這種人為疏漏,變成機器會自動抓出來、擋下來的失敗。
二、遷移與函式:依分支自動部署到不同環境
第二張心智圖,是「哪個分支的變更,自動部署到哪套環境」。一個典型的兩段式部署策略如下:
Git 分支 GitHub Actions 自動做的事 目標環境
─────────────────────────────────────────────────────────────────────
PR(對 main/develop) ──► 驗證:db reset + gen types + diff + lint (只驗證,不部署)
│
develop 分支(push) ──► link staging → db push → functions deploy Staging
│
main 分支(push) ──► link prod → db push --dry-run → db push
→ functions deploy(+ 手動審核關卡) Production
三層各司其職:
- PR 階段只驗證、不部署:確保遷移能乾淨重播、型別有同步、
db lint沒問題,把關進 merge 的品質。 - develop → Staging:merge 進 develop 就自動部署到長期存在的 staging 專案,做整合測試。
- main → Production:merge 進 main 才部署到正式環境,通常加
--dry-run先看清楚將執行的 SQL、並用 GitHub Environment 的手動審核關卡把關。
三、關鍵術語與 secret 速查
CI 要能代表你操作雲端,需要幾個關鍵的 secret 與 action。先建立對照:
| 名稱 | 角色 |
|---|---|
supabase/setup-cli@v1 | 官方 GitHub Action,在 runner 上安裝 Supabase CLI |
SUPABASE_ACCESS_TOKEN | 個人存取令牌,CI 的身分憑證(取代本機的 supabase login) |
PROJECT_ID(project-ref) | 專案 Reference ID,指定 link 要連到哪一個專案 |
SUPABASE_DB_PASSWORD | 資料庫密碼,db push 套用遷移時需要 |
git diff --exit-code | 有差異就以非 0 退出,用來讓「型別沒同步」變成 CI 失敗 |
一句話記法:access token 是「我是誰」、project-ref 是「連哪個專案」、DB password 是「進那個資料庫」——三者分工,全部存進 GitHub Secrets,絕不寫死在 workflow 裡。
實作範例
下面一步步把三份 workflow 建起來:① PR 驗證(型別守門 + lint) → ② 部署到 Staging → ③ 部署到 Production。所有檔案放在 .github/workflows/ 底下。
一、準備 GitHub Secrets
在動手寫 workflow 前,先到 GitHub 儲存庫的 Settings → Secrets and variables → Actions 新增這幾個 secret(名稱可自訂,但要和 workflow 對得上):
SUPABASE_ACCESS_TOKEN 個人存取令牌(Dashboard → Account → Access Tokens 產生)
STAGING_PROJECT_ID staging 專案的 Reference ID
STAGING_DB_PASSWORD staging 資料庫密碼
PRODUCTION_PROJECT_ID production 專案的 Reference ID
PRODUCTION_DB_PASSWORD production 資料庫密碼
SUPABASE_ACCESS_TOKEN 是所有雲端操作的身分憑證,一組通常可共用;PROJECT_ID 與 DB_PASSWORD 則依 staging/production 各存一份,分開管理能避免誤推。
二、把型別生成整合進本機工作流(package.json)
在寫 CI 之前,先讓本機也能一鍵生成型別,這樣「schema 改完就順手重生型別再 commit」才會變成習慣。在 package.json 加入 scripts:
{
"scripts": {
"types:local": "supabase gen types typescript --local > database.types.ts",
"types:remote": "supabase gen types typescript --linked > database.types.ts"
}
}
日常流程就變成:改遷移 → supabase db reset 驗證 → npm run types:local 重生型別 → git add 兩者一起 commit。CI 要守的,正是「你有沒有乖乖做最後這步」。
三、Workflow ①:PR 驗證(型別守門 + lint)
第一份 workflow 在每個 PR 上跑,不碰任何雲端專案——它只在 runner 本機跑一套 Supabase,驗證遷移、型別、lint 三件事:
# .github/workflows/ci.yml
name: CI
on:
pull_request:
branches: [main, develop]
workflow_dispatch:
jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Supabase CLI
uses: supabase/setup-cli@v1
with:
version: latest
# 只啟動資料庫,比完整 supabase start 更輕量,適合 CI
- name: Start local database
run: supabase db start
# 在乾淨環境重播所有遷移,驗證遷移能從零套起來
- name: Verify migrations
run: supabase db reset
# 從最新 schema 重新生成型別,覆蓋 repo 裡那份
- name: Generate TypeScript types
run: supabase gen types typescript --local > database.types.ts
# 守門:若生成結果和 commit 進來的不同,git diff 以非 0 退出、CI 失敗
- name: Check types are in sync
run: |
if ! git diff --exit-code database.types.ts; then
echo "::error::型別已過期。請在本機執行 npm run types:local 並提交變更。"
exit 1
fi
# schema 語法與安全性檢查
- name: Lint database
run: supabase db lint
- name: Stop local stack
run: supabase stop
if: always()
逐段講解:
supabase/setup-cli@v1:官方 action,負責在 runner 上裝好 CLI,version: latest取最新版(正式環境可釘特定版本以求穩定)。supabase db start+db reset:在 runner 本機起一套資料庫、把所有遷移重播一遍——這一步本身就在驗證「遷移能從零乾淨套起來」。gen types+git diff --exit-code:本篇的主角。生成型別後比對差異,有差就失敗。注意if ! git diff --exit-code ...; then ... exit 1——git diff --exit-code在有差異時回傳非 0,我們據此印出明確錯誤訊息並讓 job 失敗。db lint:順手做 schema 的語法與安全性檢查(如隱式型別轉換、SQL 注入風險等)。supabase stopwithif: always():無論前面成功或失敗都收拾環境。
四、Workflow ②:部署到 Staging(develop 分支)
第二份 workflow 在 push 進 develop 時觸發,把遷移與函式部署到 staging 專案:
# .github/workflows/deploy-staging.yml
name: Deploy to Staging
on:
push:
branches: [develop]
workflow_dispatch:
jobs:
deploy-staging:
runs-on: ubuntu-latest
environment: staging
env:
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
SUPABASE_DB_PASSWORD: ${{ secrets.STAGING_DB_PASSWORD }}
steps:
- uses: actions/checkout@v4
- uses: supabase/setup-cli@v1
with:
version: latest
# 用 project-ref 連結到 staging 專案
- name: Link to staging project
run: supabase link --project-ref ${{ secrets.STAGING_PROJECT_ID }}
# 部署前先看一眼本機與遠端的遷移狀態對照
- name: Check migration status
run: supabase migration list
# 把尚未套用的遷移推到 staging
- name: Push migrations
run: supabase db push
# 部署所有 Edge Functions 到 staging
- name: Deploy Edge Functions
run: supabase functions deploy
逐段講解:
env:區塊放認證:把SUPABASE_ACCESS_TOKEN與SUPABASE_DB_PASSWORD設在 job 層級的env,底下每個 step 的 CLI 指令都讀得到——這是 CI 非互動認證的關鍵,CLI 靠環境變數裡的 token 代表你操作雲端。supabase link --project-ref ...:用 staging 的 project-ref 把這次操作指向 staging 專案。supabase db push:把本機supabase/migrations/裡尚未套用的遷移,套到 staging 資料庫。supabase functions deploy(不接名稱):部署supabase/functions/底下所有函式;若只想部署單一函式,可寫supabase functions deploy <name>。environment: staging:對應 GitHub 的 Environment 設定,可在此掛上保護規則(雖然 staging 通常不需人工審核)。
五、Workflow ③:部署到 Production(main 分支)
第三份 workflow 在 push 進 main 時觸發,部署到正式環境——和 staging 幾乎一樣,但多了兩道保險:--dry-run 先看 SQL、以及靠 environment: production 掛上手動審核關卡:
# .github/workflows/deploy-production.yml
name: Deploy to Production
on:
push:
branches: [main]
workflow_dispatch:
jobs:
deploy-production:
runs-on: ubuntu-latest
environment: production # 在 GitHub 設定為需人工審核的 Environment
env:
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
SUPABASE_DB_PASSWORD: ${{ secrets.PRODUCTION_DB_PASSWORD }}
steps:
- uses: actions/checkout@v4
- uses: supabase/setup-cli@v1
with:
version: latest
- name: Link to production project
run: supabase link --project-ref ${{ secrets.PRODUCTION_PROJECT_ID }}
# 乾跑:只顯示將執行哪些 SQL,不實際套用——上生產前的最後一眼
- name: Dry-run migration push
run: supabase db push --dry-run
# 確認無誤後,實際套用遷移到 production
- name: Push migrations
run: supabase db push
- name: Deploy Edge Functions
run: supabase functions deploy
和 staging 版的差異,正是「上生產該有的謹慎」:
environment: production:在 GitHub 的 Environment 設定裡,可為 production 加上「required reviewers(必要審核者)」,讓這個 job 在真正執行前卡住、等人按核准——這是防止破壞性變更直接自動衝上正式環境的重要關卡。supabase db push --dry-run:先乾跑一次,把「將要執行的 SQL」印出來讓你(或 log)確認,不會實際改動資料庫;確認無誤,下一步才真正db push。
六、進階:排程自動更新型別(選用)
除了「PR 守門」,你也可以讓 CI 定期從 production 生成最新型別、若有變化就自動開一個 PR。這對「schema 偶爾被 Dashboard 直接改動」的團隊很有用:
# .github/workflows/update-types.yml
name: Update Database Types
on:
schedule:
- cron: '0 2 * * *' # 每天 UTC 02:00
workflow_dispatch:
jobs:
update-types:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: supabase/setup-cli@v1
with:
version: latest
# 直接用 project-id 生成,不必先 link
- name: Generate types from production
run: |
supabase gen types typescript \
--project-id ${{ secrets.PRODUCTION_PROJECT_ID }} \
> database.types.ts
env:
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
# 型別有變才自動開 PR
- name: Create PR if types changed
uses: peter-evans/create-pull-request@v6
with:
commit-message: "chore: update database type definitions"
title: "chore: update database type definitions"
branch: "chore/update-database-types"
delete-branch: true
這裡用到 supabase gen types typescript --project-id <id>——不必先 link,直接指定專案生成,適合這種一次性任務。
常見錯誤與最佳實踐
坑一:改了 schema 卻忘了重新生成型別、直接 commit。
這是最常見、也最隱蔽的坑:程式碼跑得起來、本機也沒報錯,直到某天前端用到新欄位、卻發現型別裡根本沒有,或用了已被刪掉的欄位、執行期才炸。正確做法:把 Workflow ① 的「gen types + git diff --exit-code」守門機制建起來——讓「型別沒同步」變成一個會失敗的 CI 檢查,而不是靠人自律。本機也搭配 npm run types:local,養成「改遷移、重生型別、一起 commit」的肌肉記憶。
坑二:access token 權限過大,或直接寫死在 workflow 裡。
把 SUPABASE_ACCESS_TOKEN 硬編碼在 yaml、或印到 log 裡,等於把雲端專案的鑰匙公開。正確做法:所有 token、project-ref、DB password 一律存進 GitHub Secrets,用 ${{ secrets.XXX }} 引用;token 以最小必要權限產生;定期輪替;若不慎外洩,立刻到 Dashboard 撤銷該 token 並重新產生。secret 絕不進 git、絕不進 log。
坑三:沒有 staging、直接把變更推 production。
圖快省事、跳過 staging,讓 main 一 merge 就衝上正式環境,一旦遷移有問題就是線上事故。正確做法:落實 staging → production 兩段式——develop 先部署 staging 做整合驗證,確認無虞才 merge 進 main 上 production;production 部署前一律 --dry-run 看清楚 SQL,並用 GitHub Environment 的必要審核者關卡,讓上生產這一步「一定有人按過核准」。
坑四:CI 只部署、不驗證,或遷移根本沒在乾淨環境測過。
只寫了 deploy workflow、卻沒有 PR 驗證,等於遷移沒經過「能不能從零乾淨重播」的考驗就上線。正確做法:Workflow ① 的 db reset(乾淨重播所有遷移)是最基本的品質關卡,務必納入;再加上 db lint 抓 schema 的語法與安全問題。先驗證、後部署,別讓沒測過的遷移直接進正式環境。
坑五:破壞性遷移沒演練,靠 CI 一鍵自動衝上生產。
CI/CD 讓部署變得太順手,反而放大了破壞性變更(drop column、改型別)的風險——merge 一下就自動生效在 production。正確做法:破壞性變更前,先在 Branching 的 preview 環境或 persistent staging 完整演練;遷移拆成「向後相容的多步」(先加新欄位、程式雙寫過渡、確認後才在後續遷移刪舊);production 部署前務必 --dry-run 確認 SQL,搭配審核關卡人工把關。
最佳實踐總結:
- 型別靠 CI 守門:
gen types+git diff --exit-code,讓「沒同步」自動變成 CI 失敗 - secret 全進 GitHub Secrets:access token、project-ref、DB password 絕不寫死、絕不進 log
- 兩段式部署:develop → staging 驗證、main → production 上線,別跳過 staging
- 先驗證、後部署:PR 一律跑
db reset+db lint,遷移先確認能乾淨重播 - 上生產要謹慎:
--dry-run看 SQL + GitHub Environment 審核關卡,破壞性變更先演練 - 細節以官方為準:action 版本、CLI 旗標、權限與計費都可能隨版本調整
小結
這是 Supabase 系列教學的第 042 篇,也是整個 CLI 段(SB-5)的收尾。上一篇《Branching 分支開發》讓每個 PR 都有一套隔離的預覽環境,解決了「開發階段」的協作隔離;這一篇則把「合併之後」的自動化補上——用 GitHub Actions 串起「型別守門 → 遷移部署 → 函式上線」的完整 CI/CD 流水線。
核心觀念濃縮成一句話:用 supabase gen types typescript + git diff --exit-code 在 CI 裡強制型別與 schema 同步;用 supabase/setup-cli 搭配 SUPABASE_ACCESS_TOKEN、project-ref、DB password 三個 secret,讓 GitHub Actions 自動跑 db lint、db push 部署遷移、functions deploy 上線函式;再以「develop → staging、main → production」的兩段式部署(加上 --dry-run 與審核關卡),把上一篇 Branching 的開發期預覽,接成一條從本機到生產、每一段都自動化又可審核的完整後端流水線。
- 型別守門:CI 生成型別後用
git diff --exit-code比對,沒同步就失敗,擋在 merge 前 - CI 認證:
SUPABASE_ACCESS_TOKEN(身分)+ project-ref(哪個專案)+ DB password(進資料庫),全存 GitHub Secrets - 自動部署:
supabase/setup-cli→db lint/db push/functions deploy,依分支部署到對應環境 - 兩段式與搭配:develop → staging、main → production,配
--dry-run與審核;Branching 管開發預覽、CI/CD 管合併推進
到這裡,Supabase CLI 與本機開發、遷移、Branching、CI/CD 這一整段就完整了:你已經能在本機用 Docker 跑起完整堆疊、用遷移管理每一次 schema 變更、給每個 PR 一套隔離預覽,並讓整條部署流水線自動化運轉。下一篇《自架部署(Docker Compose)》,我們會離開 Supabase 雲端,走進另一條路線——用 Docker Compose 把整套 Supabase 服務自己架在自己的伺服器上,聊聊自架的架構、設定與取捨。