你可能有一個私有專案放 A 儲存庫,裡面是 Astro、Hugo 之類的靜態網站原始程式碼。你不希望公開這些 code,但你又想把 build 出來的 HTML/CSS 推送到另一個公開的 B 儲存庫,用它來開 GitHub Pages。
每次改完 code 都要手動下 npm run build,然後把檔案複製到 B 專案再 push 上去,這流程重複幾次就會讓人想放棄。
其實你可以用 GitHub Actions 自動搞定這一切。
部署流程一覽
這套自動化流程的基本原理如下:
- 觸發 當你將原始程式碼 push 到 A 儲存庫(Repo A)時,GitHub Actions 會自動啟動工作流。
- 編譯 在 GitHub 的虛擬環境(Runner)中安裝專案相依性,並執行
npm run build編譯出靜態網頁檔案(通常在dist資料夾)。 - 驗證與推送 根據你選擇的認證方案(方案一:SSH Deploy Key,或方案二:Fine-grained tokens),Actions 會自動設定連線驗證,並在
dist底下建立CNAME網域檔,最後以「強制推送 (Force Push)」的方式將所有編譯好的網頁檔案推送到 B 儲存庫(Repo B)的部署分支。 - 釋出 B 儲存庫收到檔案後,GitHub Pages 就會自動抓取這些靜態檔案,並透過你的自訂網域
example.com發布出去。
權限防線:選擇最安全的認證方式
跨專案推送檔案需要身分驗證。在動手之前,我們要先選好驗證方式。常見的有兩種:
| 認證方式 | 權限範圍 | 安全性 | 過期機制 | 缺點 |
|---|---|---|---|---|
| Fine-grained tokens | 僅限選定的單一專案 | ⚠️ 高 | 可自訂(亦可設為不會過期) | 需注意 Token 生命週期,若無過期則風險較高 |
| SSH Deploy Key | 僅限該儲存庫 | 高 | 不會過期(除非手動撤銷) | 設定手續稍微多一些 |
該選哪一種?取決於你比較在意什麼:
- 想要設定簡單、走 HTTPS 傳輸 → 選 Fine-grained tokens(細粒度個人存取權杖)
- 想要最高安全性,且不想把個人帳號 Token 授權給 Actions 專案 → 選 Deploy Key(部署金鑰)
以下我們會先以 Deploy Key 為例進行設定,並在後面補充 Fine-grained tokens 的設定方式。
方案一:使用 SSH Deploy Key 部署
這是最安全且最不需要維護的方案。Deploy Key 本質上是一對 SSH keypair,你把私鑰給 Repo A(來源),公鑰給 Repo B(目標)。Repo A 的 Actions 就能憑藉私鑰把檔案推送到 Repo B。
步驟一:產生 SSH Key Pair
在你的電腦打開終端機,執行以下指令產生金鑰:
# 產生一組 ed25519 格式的 SSH 金鑰
ssh-keygen -t ed25519 -C "github-actions-deploy" -f ./id_deploy
你會在目錄下得到兩個檔案:
id_deploy:私鑰,千萬不能外流。id_deploy.pub:公鑰,準備要放到 GitHub 上的。
步驟二:設定 Repo B 的部署金鑰
你要告訴 Repo B:拿著這支公鑰的人,擁有寫入檔案的權限。
- 開啟 Repo B 的 GitHub 頁面。
- 進入 Settings(設定)> Deploy keys(部署金鑰)。
- 點選 Add deploy key
- Title 輸入
Repo A Deploy Action。 - Key 欄位貼上
id_deploy.pub的完整內容。 - Allow write access 一定要勾選,否則 Actions 沒辦法推 code 上來。
- Title 輸入
步驟三:設定 Repo A 的 Actions Secrets
接著你要把私鑰給 Repo A,讓它在跑 Workflow 時能透過身分驗證。
- 開啟 Repo A 的 GitHub 頁面。
- 進入 Settings > Secrets and variables > Actions。
- 點選 New repository secret
- Name 填
ACTIONS_DEPLOY_KEY。 - Value 貼上
id_deploy(私鑰)的完整內容。
- Name 填
設定完之後,就可以把電腦裡暫存的 id_deploy 與 id_deploy.pub 刪掉了。
步驟四:撰寫 GitHub Actions Workflow
我們要在 Repo A 建立工作流檔案。當你 push 到 main 分支時,它會自動 build 專案,然後使用我們配置的 SSH 私鑰推送到 Repo B。
在 Repo A 建立 .github/workflows/deploy.yml 檔案:
name: Deploy Website
on:
push:
branches:
- main # 當 main 分支有 push 時觸發
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout source code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 24
- name: Install dependencies and build
run: |
npm ci
npm run build # 產生靜態檔案,通常輸出到 dist 資料夾
- name: Configure SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ACTIONS_DEPLOY_KEY }}" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
ssh-keyscan github.com >> ~/.ssh/known_hosts # 將 GitHub 加入信任主機,避免連線時詢問
- name: Deploy to B repo
run: |
cd dist
echo "example.com" > CNAME # 寫入自訂網域,防止網域設定被覆蓋
git init -b main
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add .
git commit -m "Deploy from Repo A Action"
git branch -M main
git push -f git@github.com:your-username/repo-b.git main:main # 以 SSH 強制推送至目標儲存庫
這段 Workflow 做了三件事:
- 設定 SSH 連線 建立
~/.ssh目錄,將 Secrets 中的ACTIONS_DEPLOY_KEY私鑰寫入為id_ed25519,並利用ssh-keyscan將 GitHub 的公鑰指紋寫入known_hosts,避免 Git 連線時因為「未確認的主機」而中斷。 - 寫入 CNAME 網域檔(這一步非常關鍵)進入 build 出來的
dist目錄,寫入一個包含你網域的CNAME檔案。如果不手動在目錄中寫入CNAME,每次 Actions 覆蓋 B 專案時,都會把你手動在 B 專案設定的自訂網域清空。 - 強制推送至 Repo B 在這個目錄中初始化一個臨時的 Git 儲存庫,並以強推(Force Push)的方式覆蓋 Repo B 的分支。
方案二:使用 Fine-grained tokens 部署
如果你覺得手動產生 SSH Key Pair 太麻煩,可以選擇用 GitHub 的 Fine-grained tokens。它能指定該 Token 只能存取特定的儲存庫,同樣能做到權限限縮。
步驟一:申請 Fine-grained tokens
- 點選你的 GitHub 頭像,進入 Settings。
- 拉到最下方,點選左側選單的 Developer settings。
- 選擇 Personal access tokens > Fine-grained tokens,點選 Generate new token。
- 設定 Token 內容:
- Token name 輸入方便辨識的名字,例如
Deploy to Repo B。 - Expiration 選擇過期時間(可依需求設定,亦可選擇 No expiration 讓其不會過期)。
- Repository access 選擇 Only select repositories,並在下拉選單中選定你的 Repo B。
- Permissions 展開 Repository permissions,找到 Contents 並將權限設為 Access: Read and write。
- Token name 輸入方便辨識的名字,例如
- 點選 Generate token,並複製產生的 Token(只會顯示一次)。
步驟二:設定 Repo A 的 Secrets
- 開啟 Repo A 的 GitHub 頁面。
- 進入 Settings > Secrets and variables > Actions。
- 點選 New repository secret
- Name 填
ACTIONS_PAT。 - Value 貼上剛才複製的 Fine-grained tokens。
- Name 填
步驟三:修改 Workflow 檔案
如果改用 Fine-grained tokens,你就不用在 Actions 裡面安裝與配置 SSH 金鑰了。你只需要在推送時將 Token 帶入 HTTPS 網址中。
將 Repo A 中的 .github/workflows/deploy.yml 內容修改為:
name: Deploy Website
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout source code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 24
- name: Install dependencies and build
run: |
npm ci
npm run build
- name: Deploy to B repo
run: |
cd dist
echo "example.com" > CNAME
git init -b main
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add .
git commit -m "Deploy from Repo A Action"
git branch -M main
# 使用 Fine-grained tokens 透過 HTTPS 方式強制推送
git push -f https://x-access-token:${{ secrets.ACTIONS_PAT }}@github.com/your-username/repo-b.git main:main
這裡的關鍵在於這一行:
https://x-access-token:${{ secrets.ACTIONS_PAT }}@github.com/your-username/repo-b.git
這是 GitHub 官方推薦在 Actions 中使用 PAT(Personal Access Token)進行 HTTPS 驗證的方法。x-access-token 是固定的使用者名稱,密碼則帶入你的 Token,這樣 Git 就能安全地完成驗證並進行強推。
設定 DNS 網域解析(兩種方案皆適用)
最後,去你的網域託管商(例如 Cloudflare 或 Namecheap)設定 DNS,把 example.com 對接到 GitHub Pages。
如果是子網域(如 blog.example.com),新增一條 CNAME 紀錄:
- Type(類型):
CNAME - Name(名稱):
blog - Target(目標):
your-username.github.io
如果是頂級網域(如 example.com),你需要設定 A 紀錄,指向 GitHub Pages 的 IP:
185.199.108.153185.199.109.153185.199.110.153185.199.111.153
結語
跨專案部署聽起來很繞,其實關鍵只有一個:建立兩個 Repo 之間的信任通道。
不論是透過 SSH Deploy Key 還是 Fine-grained tokens,都能在保證帳號安全的前提下完成自動化部署。這套流程設定好一次,以後你就只需要專心寫 code 了。