不只是上傳!打造 WordPress『零停機』CI/CD 部署流水線:GitHub Actions 進階實戰
嗨,我是浪花科技的 Eric。身為一個每天在程式碼跟伺服器之間打滾的工程師,我最怕聽到的不是 bug,而是客戶在電話那頭焦急地說:「欸 Eric,我剛剛看到網站好像破版了,一下下又好了,怎麼回事?」
這「一下下」的瞬間,通常就是我們正在用傳統 FTP 更新檔案的尷尬時刻。檔案一個個上傳,新舊檔案交替,網站就像在手術台上開膛剖肚,使用者一不小心就看到了血淋淋的半成品。這種體驗,不僅不專業,更有可能在關鍵時刻嚇跑你的潛在客戶。
很多開發者可能已經看過我們另一篇 GitHub Actions 的入門文章,學會了如何自動化上傳檔案。但今天,我們要談的是進階戰術:如何打造一個「零停機」(Zero-Downtime)的 CI/CD 自動部署流水線。這不只是把檔案丟上伺服器那麼簡單,而是一套確保網站更新過程「如絲般滑順」,使用者完全無感的專業作法。準備好了嗎?讓我們把手弄髒,開始搞定這個工程師的浪漫吧!
為何『手動 FTP』是專業團隊的惡夢?
在我們深入探討解決方案之前,我想先囉嗦幾句,讓我們再次正視傳統 FTP/SFTP 手動部署的根本問題。這不只是效率問題,更是風險管理的問題。
- 更新過程中的「空窗期」: 當你上傳檔案時,尤其是檔案數量很多的時候,伺服器上的程式碼會處於一個「半新半舊」的尷尬狀態。如果使用者剛好在這幾秒或幾十秒內瀏覽網站,很可能會看到 PHP 錯誤、樣式錯亂,甚至是致命的「死亡白畫面」。
- 人為失誤的無限可能: 你有沒有忘記上傳某個關鍵檔案的經驗?或者,上傳到錯誤的資料夾?(別騙我,我們都做過)。手動操作,就意味著失誤的機率永遠存在,而且通常發生在最不想發生的時候,比如週五下班前。
- 缺乏版本控制與回滾機制: 如果更新後發現重大問題,你該怎麼辦?再手動把舊版本的檔案一個個傳回去嗎?這過程不僅慢,而且壓力山大。一個專業的部署流程,應該要能在一瞬間就將網站恢復到上一個穩定版本。
所以,我們追求的 CI/CD 自動部署(GitHub Actions),目標不僅僅是「快」,更是「穩」與「可靠」。
CI/CD 的真正威力:原子部署 (Atomic Deployment)
「原子部署」聽起來很高科技,但概念其實很單純。在化學中,「原子」是不可再分割的最小單位;在部署的世界裡,「原子部署」意味著整個更新過程是一個「不可分割的操作」。要嘛就是 100% 成功,要嘛就是完全沒發生,不會有中間狀態。
它是如何運作的?
想像一下你的伺服器結構,傳統作法是直接覆寫 /var/www/html 裡的檔案。而原子部署則是這樣玩的:
- 建立新的發佈目錄: 我們的自動化腳本不會直接碰線上正在運行的程式碼。它會在旁邊建立一個新的、以時間戳或 Git Commit HASH 命名的資料夾,例如
/var/www/releases/20250915113000/。 - 同步最新程式碼: 將 GitHub 上的最新程式碼完整地同步到這個新的發佈目錄中。
- 切換符號連結 (Symbolic Link): 這是最關鍵的一步。你網站的根目錄(例如
/var/www/html)其實不是一個真正的資料夾,而是一個指向某個發佈目錄的「捷徑」(符號連結)。當新版本的程式碼準備就緒後,我們只需要一個指令,將這個捷徑從指向舊的發佈目錄(/var/www/releases/old_version/)瞬間切換到新的發佈目錄(/var/www/releases/20250915113000/)。 - 清理舊版本: 確認新版本運行正常後,自動化腳本可以刪除幾個版本之前的舊發佈目錄,釋放伺服器空間。
整個切換過程只在毫秒之間,使用者完全感受不到任何中斷。如果新版本有問題,回滾也只是把符號連結指回上一個版本的目錄而已,同樣是瞬間完成。這才是企業級的部署策略!
GitHub Actions 實戰:打造你的零停機部署流水線
理論說完了,來點實際的。接下來,我會帶你一步步設定 GitHub Actions,實現上面提到的原子部署。我們這次不用 FTP Action,改用更強大、更靈活的 SSH 搭配 rsync。
Step 1: 準備工作 – SSH 金鑰與 Repository Secrets
首先,我們需要讓 GitHub Actions 能夠免密碼登入你的伺服器。
- 產生 SSH 金鑰對: 在你的本機電腦上執行
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"。這會產生id_rsa(私鑰)和id_rsa.pub(公鑰)兩個檔案。 - 在伺服器上設定公鑰: 將
id_rsa.pub檔案的內容,複製到你伺服器上~/.ssh/authorized_keys檔案中。 - 在 GitHub 設定 Secrets: 進入你的專案 GitHub Repo > Settings > Secrets and variables > Actions。點擊 “New repository secret”,建立以下幾個 Secrets:
SSH_PRIVATE_KEY:貼上你本機id_rsa檔案的完整內容。SSH_HOST:你的伺服器 IP 或網域。SSH_USER:你登入伺服器的使用者名稱(例如:ubuntu)。DEPLOY_PATH:部署的根目錄,例如/var/www/my-wordpress-site。
工程師的小囉嗦: 千萬不要把私鑰直接寫在 YAML 檔案裡,這等於是把家裡鑰匙掛在門口。使用 GitHub Secrets 是唯一安全的方式。
Step 2: 編寫你的 Workflow YAML 檔案
在你的專案根目錄下建立 .github/workflows/deploy.yml 檔案,並貼上以下內容。這份設定檔包含了編譯前端資源(如果需要)以及執行原子部署的完整邏輯。
name: Deploy WordPress with Zero Downtime
on:
push:
branches:
- main # 當 main 分支有更新時觸發
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: 1. Checkout Code
uses: actions/checkout@v3
- name: 2. Setup PHP for Composer
uses: shivammathur/setup-php@v2
with:
php-version: '8.1'
extensions: mbstring, zip
tools: composer
- name: 3. Install Composer Dependencies
run: composer install --prefer-dist --no-progress --no-dev
- name: 4. Setup Node.js for Asset Compilation
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: 5. Install NPM Dependencies
run: npm ci
- name: 6. Build Frontend Assets
run: npm run build
- name: 7. Deploy to Server with Atomic Swap
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
set -e # 如果任何指令失敗,就立刻停止
# 定義變數
DEPLOY_PATH=${{ secrets.DEPLOY_PATH }}
RELEASE_DIR="$DEPLOY_PATH/releases/$(date +%Y%m%d%H%M%S)"
CURRENT_SYMLINK="$DEPLOY_PATH/current"
echo "Deploying to $RELEASE_DIR"
# 建立新的 release 資料夾
mkdir -p $RELEASE_DIR
# 使用 rsync 同步檔案(排除 .git 和 node_modules)
rsync -avz --exclude='.git' --exclude='node_modules' ./ $RELEASE_DIR/
# 處理共享檔案/資料夾,例如 wp-config.php 和 uploads
# 假設 shared 資料夾已經在 DEPLOY_PATH 中建立好
ln -nfs $DEPLOY_PATH/shared/wp-config.php $RELEASE_DIR/wp-config.php
ln -nfs $DEPLOY_PATH/shared/uploads $RELEASE_DIR/wp-content/uploads
# 原子性地切換符號連結
ln -nfs $RELEASE_DIR $CURRENT_SYMLINK
echo "Deployment successful! New release is live."
# 清理舊的 releases (保留最近的 5 個)
cd $DEPLOY_PATH/releases && ls -t | tail -n +6 | xargs -r rm -rf
Step 3: 拆解 Workflow – 這到底在幹嘛?
讓我們一步步解析這份 YAML 檔:
- 步骤 1-3: 處理後端依賴: 我們先 Checkout 程式碼,然後設定好 PHP 環境,並執行
composer install。這對於有使用 Composer 管理 PHP 套件的現代 WordPress 開發流程非常重要。 - 步骤 4-6: 編譯前端資源: 接著設定 Node.js 環境,安裝 npm 依賴,並執行
npm run build。這會將你的 SASS/SCSS、TypeScript/ES6+ 等原始碼編譯成瀏覽器看得懂的 CSS 和 JavaScript。部署到伺服器的應該是編譯後的結果,而不是原始碼。 - 步骤 7: 部署魔法: 這是原子部署的核心。
set -e: 這是一個保險,確保腳本中任何一個指令失敗,整個部署過程就會中止,不會執行到一半。- 變數定義: 我們定義了部署路徑、新的 release 資料夾名稱(用時間戳命名確保唯一性)、以及符號連結的路徑。
- rsync 同步:
rsync是個比scp更聰明的同步工具,它只會傳輸有變動的檔案,速度更快。我們也排除了不需要上傳的.git和node_modules資料夾。 - 共享檔案處理: 像
wp-config.php和uploads資料夾這種每個版本都共用、且不應被版本控制的內容,我們將它們放在一個shared資料夾中,然後用符號連結連到新的 release 目錄裡。 - 原子切換:
ln -nfs $RELEASE_DIR $CURRENT_SYMLINK這一行指令就是整個流程的精髓,它原子性地將current這個連結指向我們剛準備好的新版本。 - 清理舊版: 最後,一個簡單的指令幫我們保留最近的 5 個版本,自動刪除更舊的,避免佔用伺服器空間。
進階加碼:整合 WP-CLI 與程式碼品質檢查
一個真正完整的 CI/CD 流程還能做得更多。例如,在部署完成後自動執行資料庫更新。
你可以在 SSH script 的最後,加入 WP-CLI 指令:
# ...接續上面的 script...
# 原子性地切換符號連結
ln -nfs $RELEASE_DIR $CURRENT_SYMLINK
# 執行資料庫更新與快取清除
cd $CURRENT_SYMLINK
wp db upgrade
wp cache flush
echo "Deployment successful! New release is live."
# ...後續清理...
這能確保你的外掛或佈景主題需要的資料庫變動,在程式碼上線的同時就立刻生效。你甚至可以在部署前加入一個步驟,執行 PHP CodeSniffer (phpcs) 來檢查程式碼是否符合 WordPress 編碼標準,如果不符合,就直接中斷部署!這能從源頭杜絕不合格的程式碼上線。
搞定!現在開始,你只要 git push 到 main 分支,GitHub Actions 就會為你執行這一整套專業、安全、零停機的部署流程。你可以泡杯咖啡,看著 Actions 的綠色勾勾亮起,然後自信地告訴客戶:「網站已更新完成!」再也不用在 FTP 的上傳進度條前膽戰心驚了。
這套流程不僅提升了你的工作效率,更重要的是,它代表了一種專業和對品質的承諾。當你的開發流程越來越穩固,你才能真正專注在創造價值,而不是處理各種部署意外。
當然,CI/CD 的世界博大精深,從多環境部署(Staging、Production)、自動化測試到容器化部署,還有很多可以探索的。如果你的團隊也想導入這樣現代化的開發流程,卻不知從何下手,或是遇到了更複雜的架構問題,歡迎與浪花科技的團隊聊聊。我們樂於分享我們的經驗,協助你打造更強健的數位產品。
延伸閱讀
- 告別手動 FTP 上傳地獄!用 GitHub Actions 打造 WordPress 自動化部署流程,優雅又高效!
- 終結滑鼠手!資深工程師的 WP-CLI 神兵利器,讓你的 WordPress 管理效率原地起飛!
- 「在我電腦明明可以跑的?」WordPress Docker 容器化部署終極教學
常見問題 (FAQ)
Q1: 什麼是「零停機部署」(Zero-Downtime Deployment)?為什麼它很重要?
零停機部署是一種網站更新策略,旨在確保在部署新版本程式碼的過程中,網站服務完全不中斷,使用者不會遇到任何錯誤或白畫面。它通常透過「原子部署」技術實現,例如先將新程式碼部署到一個新的目錄,然後瞬間切換一個符號連結(symlink)來啟用新版本。這對於提供穩定、專業的使用者體驗至關重要,特別是對於電商或高流量網站。
Q2: 這篇文章的「原子部署」方法跟一般的 FTP 自動上傳有什麼核心差異?
核心差異在於「更新的原子性」。一般的 FTP 自動上傳是逐一覆寫伺服器上的檔案,這會導致在更新期間,網站程式碼處於「半新半舊」的不穩定狀態。而原子部署則是將整個新版本準備好在一個獨立的目錄中,然後透過一個單一、瞬間完成的操作(切換符號連結)來啟用新版本。如果部署失敗,新版本根本不會上線;如果需要回滾,也只需將連結指回舊版本即可,過程既快速又安全。
Q3: 我的 WordPress 專案沒有用 Composer 或 npm,還能使用這套流程嗎?
絕對可以!這套 CI/CD 流程是模組化的。如果你的專案比較單純,沒有使用 Composer 或 npm,你只需要在 deploy.yml 檔案中,將「Install Composer Dependencies」、「Setup Node.js」、「Install NPM Dependencies」和「Build Frontend Assets」這幾個步驟 (steps) 刪除即可。剩下的部署邏輯(使用 SSH 和 rsync 進行原子部署)完全適用,依然能享受到零停機部署的好處。
Q4: wp-config.php 和 uploads 資料夾為什麼要特別處理?
wp-config.php 包含了資料庫連線等敏感資訊,不應該被存放在公開的 Git 儲存庫中。uploads 資料夾則包含了使用者上傳的媒體檔案,體積龐大且會不斷變動,也不適合用 Git 管理。因此,最佳實踐是將這兩者放在部署目錄之外的一個共享資料夾 (shared),然後在每次部署新版本時,透過符號連結將它們鏈接到新的發佈目錄中,確保每個版本都能存取到同一個設定檔和同一份媒體檔案。






